More grading updates
This commit is contained in:
@@ -9,6 +9,8 @@ import com.jaytux.grader.data.v2.Courses
|
|||||||
import com.jaytux.grader.data.v2.NumericGrade
|
import com.jaytux.grader.data.v2.NumericGrade
|
||||||
import com.jaytux.grader.data.v2.v2Tables
|
import com.jaytux.grader.data.v2.v2Tables
|
||||||
import dev.dirs.ProjectDirectories
|
import dev.dirs.ProjectDirectories
|
||||||
|
import org.jetbrains.exposed.v1.core.dao.id.EntityID
|
||||||
|
import org.jetbrains.exposed.v1.core.eq
|
||||||
import org.jetbrains.exposed.v1.jdbc.SchemaUtils
|
import org.jetbrains.exposed.v1.jdbc.SchemaUtils
|
||||||
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
|
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
|
||||||
import kotlin.getValue
|
import kotlin.getValue
|
||||||
@@ -18,6 +20,9 @@ import kotlin.io.path.exists
|
|||||||
import org.jetbrains.exposed.v1.jdbc.Database
|
import org.jetbrains.exposed.v1.jdbc.Database
|
||||||
import org.jetbrains.exposed.v1.jdbc.batchInsert
|
import org.jetbrains.exposed.v1.jdbc.batchInsert
|
||||||
import org.jetbrains.exposed.v1.jdbc.transactions.TransactionManager
|
import org.jetbrains.exposed.v1.jdbc.transactions.TransactionManager
|
||||||
|
import org.jetbrains.exposed.v1.jdbc.update
|
||||||
|
import org.jetbrains.exposed.v1.migration.jdbc.MigrationUtils
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
object Database {
|
object Database {
|
||||||
val dataDir: String = ProjectDirectories.from("com", "jaytux", "grader").dataDir.also {
|
val dataDir: String = ProjectDirectories.from("com", "jaytux", "grader").dataDir.also {
|
||||||
@@ -43,6 +48,8 @@ object Database {
|
|||||||
it[CategoricGrades.id]
|
it[CategoricGrades.id]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var passId: EntityID<UUID>? = null
|
||||||
|
var bId: EntityID<UUID>? = null
|
||||||
CategoricGradeOptions.batchInsert(
|
CategoricGradeOptions.batchInsert(
|
||||||
listOf("Pass", "Fail").mapIndexed { idx, it -> it to pf app idx } +
|
listOf("Pass", "Fail").mapIndexed { idx, it -> it to pf app idx } +
|
||||||
listOf("A (Excellent)", "B (Good)", "C (Satisfactory)", "D (Poor)", "F (Fail)").mapIndexed { idx, it -> it to af app idx }
|
listOf("A (Excellent)", "B (Good)", "C (Satisfactory)", "D (Poor)", "F (Fail)").mapIndexed { idx, it -> it to af app idx }
|
||||||
@@ -50,7 +57,15 @@ object Database {
|
|||||||
this[CategoricGradeOptions.option] = it.first
|
this[CategoricGradeOptions.option] = it.first
|
||||||
this[CategoricGradeOptions.gradeId] = it.second
|
this[CategoricGradeOptions.gradeId] = it.second
|
||||||
this[CategoricGradeOptions.index] = it.third
|
this[CategoricGradeOptions.index] = it.third
|
||||||
|
}.forEach {
|
||||||
|
when(it[CategoricGradeOptions.option]) {
|
||||||
|
"Pass" -> passId = it[CategoricGradeOptions.id]
|
||||||
|
"B (Good)" -> bId = it[CategoricGradeOptions.id]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CategoricGrades.update(where = { CategoricGrades.id eq pf }) { it[CategoricGrades.defaultOption] = passId!! }
|
||||||
|
CategoricGrades.update(where = { CategoricGrades.id eq af }) { it[CategoricGrades.defaultOption] = bId!! }
|
||||||
}
|
}
|
||||||
|
|
||||||
if(NumericGrade.count() == 0L) {
|
if(NumericGrade.count() == 0L) {
|
||||||
|
|||||||
@@ -142,6 +142,7 @@ object PeerEvaluationS2SEvaluations : UUIDTable("peerEvalS2SEvals") {
|
|||||||
|
|
||||||
object CategoricGrades : UUIDTable("categoricGrades") {
|
object CategoricGrades : UUIDTable("categoricGrades") {
|
||||||
val name = varchar("name", 50).uniqueIndex()
|
val name = varchar("name", 50).uniqueIndex()
|
||||||
|
val defaultOption = reference("default_option_id", CategoricGradeOptions.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
object CategoricGradeOptions : UUIDTable("categoricGradeOpts") {
|
object CategoricGradeOptions : UUIDTable("categoricGradeOpts") {
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ class CategoricGrade(id: EntityID<UUID>) : UUIDEntity(id) {
|
|||||||
companion object : EntityClass<UUID, CategoricGrade>(CategoricGrades)
|
companion object : EntityClass<UUID, CategoricGrade>(CategoricGrades)
|
||||||
|
|
||||||
var name by CategoricGrades.name
|
var name by CategoricGrades.name
|
||||||
|
var default by CategoricGradeOption referencedOn CategoricGrades.defaultOption
|
||||||
|
|
||||||
val options by CategoricGradeOption referrersOn CategoricGradeOptions.gradeId orderBy CategoricGradeOptions.index
|
val options by CategoricGradeOption referrersOn CategoricGradeOptions.gradeId orderBy CategoricGradeOptions.index
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ import com.jaytux.grader.GroupGrading
|
|||||||
import com.jaytux.grader.PeerEvalGrading
|
import com.jaytux.grader.PeerEvalGrading
|
||||||
import com.jaytux.grader.SoloGrading
|
import com.jaytux.grader.SoloGrading
|
||||||
import com.jaytux.grader.data.v2.AssignmentType
|
import com.jaytux.grader.data.v2.AssignmentType
|
||||||
|
import com.jaytux.grader.data.v2.CategoricGrade
|
||||||
|
import com.jaytux.grader.data.v2.CategoricGradeOption
|
||||||
|
import com.jaytux.grader.data.v2.CategoricGradeOptions
|
||||||
import com.jaytux.grader.viewmodel.EditionVM
|
import com.jaytux.grader.viewmodel.EditionVM
|
||||||
import com.jaytux.grader.viewmodel.Navigator
|
import com.jaytux.grader.viewmodel.Navigator
|
||||||
import com.jaytux.grader.viewmodel.UiGradeType
|
import com.jaytux.grader.viewmodel.UiGradeType
|
||||||
@@ -47,6 +50,8 @@ fun AssignmentsView(vm: EditionVM, token: Navigator.NavToken) = Row(Modifier.fil
|
|||||||
var addingRubric by remember { mutableStateOf(false) }
|
var addingRubric by remember { mutableStateOf(false) }
|
||||||
var editingRubric by remember { mutableStateOf(-1) }
|
var editingRubric by remember { mutableStateOf(-1) }
|
||||||
var updatingGrade by remember { mutableStateOf(false) }
|
var updatingGrade by remember { mutableStateOf(false) }
|
||||||
|
var renaming by remember { mutableStateOf(false) }
|
||||||
|
var deleting by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
val navToGrading = lambda@{
|
val navToGrading = lambda@{
|
||||||
if(assignment == null) return@lambda
|
if(assignment == null) return@lambda
|
||||||
@@ -58,7 +63,7 @@ fun AssignmentsView(vm: EditionVM, token: Navigator.NavToken) = Row(Modifier.fil
|
|||||||
}
|
}
|
||||||
|
|
||||||
Surface(Modifier.weight(0.25f).fillMaxHeight(), tonalElevation = 7.dp) {
|
Surface(Modifier.weight(0.25f).fillMaxHeight(), tonalElevation = 7.dp) {
|
||||||
ListOrEmpty(assignments, { Text("No groups yet.") }) { idx, it ->
|
ListOrEmpty(assignments, { Text("No assignments yet.") }) { idx, it ->
|
||||||
QuickAssignment(idx, it, vm)
|
QuickAssignment(idx, it, vm)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,8 +78,21 @@ fun AssignmentsView(vm: EditionVM, token: Navigator.NavToken) = Row(Modifier.fil
|
|||||||
val peerEvalData by vm.asPeerEvaluation.entity
|
val peerEvalData by vm.asPeerEvaluation.entity
|
||||||
var updatingPeerEvalGrade by remember { mutableStateOf(false) }
|
var updatingPeerEvalGrade by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
Text(assignment.assignment.name, style = MaterialTheme.typography.headlineMedium)
|
Column {
|
||||||
Text("Deadline: ${assignment.assignment.deadline.format(fmt)}", Modifier.padding(top = 5.dp).clickable { updatingDeadline = true }, fontStyle = FontStyle.Italic)
|
Row(Modifier.height(IntrinsicSize.Min)) {
|
||||||
|
EditableText(
|
||||||
|
assignment.assignment.name, style = MaterialTheme.typography.headlineMedium,
|
||||||
|
canSave = { it.isNotBlank() && (it == assignment.assignment.name || !assignments.any { x -> x.assignment.name == it }) }
|
||||||
|
) {
|
||||||
|
vm.modAssignment(assignment.assignment, it, null)
|
||||||
|
}
|
||||||
|
Spacer(Modifier.width(10.dp))
|
||||||
|
IconButton(Delete, "Delete assignment", Modifier.align(Alignment.CenterVertically)) {
|
||||||
|
deleting = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Text("Deadline: ${assignment.assignment.deadline.format(fmt)}", Modifier.padding(top = 5.dp).clickable { updatingDeadline = true }, fontStyle = FontStyle.Italic)
|
||||||
|
}
|
||||||
Row {
|
Row {
|
||||||
Text("${assignment.assignment.type.display} using grading ", Modifier.align(Alignment.CenterVertically))
|
Text("${assignment.assignment.type.display} using grading ", Modifier.align(Alignment.CenterVertically))
|
||||||
Surface(shape = MaterialTheme.shapes.small, tonalElevation = 10.dp) {
|
Surface(shape = MaterialTheme.shapes.small, tonalElevation = 10.dp) {
|
||||||
@@ -192,6 +210,15 @@ fun AssignmentsView(vm: EditionVM, token: Navigator.NavToken) = Row(Modifier.fil
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(deleting) {
|
||||||
|
if(assignment == null) deleting = false
|
||||||
|
else {
|
||||||
|
ConfirmDeleteDialog("an assignment", { deleting = false }, { vm.rmAssignment(assignment.assignment) }) {
|
||||||
|
Text("${assignment.assignment.type.display} \"${assignment.assignment.name}\"")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val fmt = LocalDateTime.Format {
|
val fmt = LocalDateTime.Format {
|
||||||
@@ -300,7 +327,7 @@ fun AddCriterionDialog(current: EditionVM.CriterionData?, vm: EditionVM, taken:
|
|||||||
OutlinedTextField(desc, { desc = it }, Modifier.fillMaxWidth(), label = { Text("Short Description") }, singleLine = true)
|
OutlinedTextField(desc, { desc = it }, Modifier.fillMaxWidth(), label = { Text("Short Description") }, singleLine = true)
|
||||||
Surface(shape = MaterialTheme.shapes.small, color = Color.White, modifier = Modifier.fillMaxWidth().padding(5.dp)) {
|
Surface(shape = MaterialTheme.shapes.small, color = Color.White, modifier = Modifier.fillMaxWidth().padding(5.dp)) {
|
||||||
Column {
|
Column {
|
||||||
GradeTypePicker(type, categories, numeric, { n, o -> vm.mkScale(n, o) }, { n, m -> vm.mkNumericScale(n, m) }, Modifier.weight(1f)) { type = it }
|
GradeTypePicker(type, categories, numeric, vm::mkScale, vm::modScale, vm::mkNumericScale, Modifier.weight(1f)) { type = it }
|
||||||
|
|
||||||
CancelSaveRow(name.isNotBlank() && (name !in taken || name == current?.criterion?.name), onClose) {
|
CancelSaveRow(name.isNotBlank() && (name !in taken || name == current?.criterion?.name), onClose) {
|
||||||
onSave(name, desc, type)
|
onSave(name, desc, type)
|
||||||
@@ -331,7 +358,7 @@ fun SetGradingDialog(name: String, current: UiGradeType, vm: EditionVM, onClose:
|
|||||||
Text("Select a grading scale for $name", style = MaterialTheme.typography.headlineSmall, modifier = Modifier.padding(bottom = 10.dp))
|
Text("Select a grading scale for $name", style = MaterialTheme.typography.headlineSmall, modifier = Modifier.padding(bottom = 10.dp))
|
||||||
Surface(shape = MaterialTheme.shapes.small, color = Color.White, modifier = Modifier.fillMaxWidth().padding(5.dp)) {
|
Surface(shape = MaterialTheme.shapes.small, color = Color.White, modifier = Modifier.fillMaxWidth().padding(5.dp)) {
|
||||||
Column {
|
Column {
|
||||||
GradeTypePicker(type, categories, numeric, { n, o -> vm.mkScale(n, o) }, { n, m -> vm.mkNumericScale(n, m) }, Modifier.weight(1f)) { type = it }
|
GradeTypePicker(type, categories, numeric, vm::mkScale, vm::modScale, vm::mkNumericScale, Modifier.weight(1f)) { type = it }
|
||||||
|
|
||||||
CancelSaveRow(true, onClose) {
|
CancelSaveRow(true, onClose) {
|
||||||
onSave(type)
|
onSave(type)
|
||||||
@@ -349,7 +376,9 @@ fun SetGradingDialog(name: String, current: UiGradeType, vm: EditionVM, onClose:
|
|||||||
@Composable
|
@Composable
|
||||||
fun GradeTypePicker(
|
fun GradeTypePicker(
|
||||||
type: UiGradeType, categories: List<UiGradeType.Categoric>, numeric: List<UiGradeType.Numeric>,
|
type: UiGradeType, categories: List<UiGradeType.Categoric>, numeric: List<UiGradeType.Numeric>,
|
||||||
mkCat: (String, List<String>) -> Unit, mkNum: (String, Double) -> Unit,
|
mkCat: (String, List<String>, Int) -> Unit,
|
||||||
|
modCat: (cat: CategoricGrade, add: List<String>, default: Int) -> Unit,
|
||||||
|
mkNum: (String, Double) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onUpdate: (UiGradeType) -> Unit
|
onUpdate: (UiGradeType) -> Unit
|
||||||
) = Column(modifier) {
|
) = Column(modifier) {
|
||||||
@@ -394,19 +423,26 @@ fun GradeTypePicker(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
(type as? UiGradeType.Categoric)?.let {
|
(type as? UiGradeType.Categoric)?.let {
|
||||||
|
var updating by remember(type, categories) { mutableStateOf<UiGradeType.Categoric?>(null) }
|
||||||
|
|
||||||
LazyColumn(Modifier.weight(1f)) {
|
LazyColumn(Modifier.weight(1f)) {
|
||||||
itemsIndexed(categories) { idx, it ->
|
itemsIndexed(categories) { idx, it ->
|
||||||
Surface(
|
Surface(
|
||||||
tonalElevation = if (selectedCategory == idx) 15.dp else 0.dp,
|
tonalElevation = if (selectedCategory == idx) 15.dp else 0.dp,
|
||||||
shape = MaterialTheme.shapes.small
|
shape = MaterialTheme.shapes.small
|
||||||
) {
|
) {
|
||||||
Column(Modifier.fillMaxWidth().clickable { selectedCategory = idx; onUpdate(it) }.padding(10.dp)) {
|
Row(Modifier.fillMaxWidth().clickable { selectedCategory = idx; onUpdate(it) }.padding(10.dp)) {
|
||||||
Text(it.grade.name, fontWeight = FontWeight.Bold)
|
Column(Modifier.weight(1f)) {
|
||||||
Text(
|
Text(it.grade.name, fontWeight = FontWeight.Bold)
|
||||||
"(${it.options.size} options)",
|
Text(
|
||||||
Modifier.padding(start = 10.dp),
|
"(${it.options.size} options; default ${it.default?.option ?: "none"})",
|
||||||
fontStyle = FontStyle.Italic
|
Modifier.padding(start = 10.dp),
|
||||||
)
|
fontStyle = FontStyle.Italic
|
||||||
|
)
|
||||||
|
}
|
||||||
|
IconButton(Edit, modifier = Modifier.align(Alignment.CenterVertically)) {
|
||||||
|
updating = it
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -417,6 +453,11 @@ fun GradeTypePicker(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(updating != null) ModCatScaleDialog(updating!!, { updating = null }) { categoric, add, i ->
|
||||||
|
modCat(categoric.grade, add, i)
|
||||||
|
updating = null
|
||||||
|
}
|
||||||
} ?: (type as? UiGradeType.Numeric)?.let {
|
} ?: (type as? UiGradeType.Numeric)?.let {
|
||||||
LazyColumn(Modifier.weight(1f)) {
|
LazyColumn(Modifier.weight(1f)) {
|
||||||
itemsIndexed(numeric) { idx, it ->
|
itemsIndexed(numeric) { idx, it ->
|
||||||
@@ -445,8 +486,8 @@ fun GradeTypePicker(
|
|||||||
|
|
||||||
if(adding) {
|
if(adding) {
|
||||||
when(type) {
|
when(type) {
|
||||||
is UiGradeType.Categoric -> AddCatScaleDialog(categories.map { it.grade.name }, { adding = false }) { name, options ->
|
is UiGradeType.Categoric -> AddCatScaleDialog(categories.map { it.grade.name }, { adding = false }) { name, options, idx ->
|
||||||
mkCat(name, options)
|
mkCat(name, options, idx)
|
||||||
}
|
}
|
||||||
is UiGradeType.Numeric -> AddNumScaleDialog(numeric.map { it.grade.name }, { adding = false }) { name, max ->
|
is UiGradeType.Numeric -> AddNumScaleDialog(numeric.map { it.grade.name }, { adding = false }) { name, max ->
|
||||||
mkNum(name, max)
|
mkNum(name, max)
|
||||||
@@ -457,7 +498,7 @@ fun GradeTypePicker(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AddCatScaleDialog(taken: List<String>, onClose: () -> Unit, onSave: (String, List<String>) -> Unit) = DialogWindow(
|
fun AddCatScaleDialog(taken: List<String>, onClose: () -> Unit, onSave: (String, List<String>, Int) -> Unit) = DialogWindow(
|
||||||
onCloseRequest = onClose,
|
onCloseRequest = onClose,
|
||||||
state = rememberDialogState(size = DpSize(750.dp, 600.dp), position = WindowPosition(Alignment.Center))
|
state = rememberDialogState(size = DpSize(750.dp, 600.dp), position = WindowPosition(Alignment.Center))
|
||||||
) {
|
) {
|
||||||
@@ -465,6 +506,7 @@ fun AddCatScaleDialog(taken: List<String>, onClose: () -> Unit, onSave: (String,
|
|||||||
var name by remember { mutableStateOf("") }
|
var name by remember { mutableStateOf("") }
|
||||||
var options by remember { mutableStateOf(listOf<String>()) }
|
var options by remember { mutableStateOf(listOf<String>()) }
|
||||||
var adding by remember { mutableStateOf("") }
|
var adding by remember { mutableStateOf("") }
|
||||||
|
var default by remember { mutableStateOf(0) }
|
||||||
|
|
||||||
Surface(Modifier.fillMaxSize()) {
|
Surface(Modifier.fillMaxSize()) {
|
||||||
Box(Modifier.fillMaxSize().padding(10.dp)) {
|
Box(Modifier.fillMaxSize().padding(10.dp)) {
|
||||||
@@ -474,8 +516,15 @@ fun AddCatScaleDialog(taken: List<String>, onClose: () -> Unit, onSave: (String,
|
|||||||
LazyColumn(Modifier.weight(1f)) {
|
LazyColumn(Modifier.weight(1f)) {
|
||||||
itemsIndexed(options) { idx, it ->
|
itemsIndexed(options) { idx, it ->
|
||||||
Row(Modifier.fillMaxWidth().padding(5.dp)) {
|
Row(Modifier.fillMaxWidth().padding(5.dp)) {
|
||||||
Text(it, Modifier.weight(1f))
|
Column(Modifier.weight(1f).align(Alignment.CenterVertically)) {
|
||||||
IconButton({ options = options.filterNot { o -> o == it } }) {
|
Text(it)
|
||||||
|
if(idx == default) Text("(default option)", Modifier.padding(start = 10.dp), fontStyle = FontStyle.Italic)
|
||||||
|
}
|
||||||
|
IconButton({ default = idx }, Modifier.align(Alignment.CenterVertically)) {
|
||||||
|
if(default == idx) Icon(CheckboxChecked, "Default option")
|
||||||
|
else Icon(CheckboxUnchecked, "Set as default")
|
||||||
|
}
|
||||||
|
IconButton({ options = options.filterNot { o -> o == it } }, Modifier.align(Alignment.CenterVertically)) {
|
||||||
Icon(Delete, "Delete grading option")
|
Icon(Delete, "Delete grading option")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -489,8 +538,77 @@ fun AddCatScaleDialog(taken: List<String>, onClose: () -> Unit, onSave: (String,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CancelSaveRow(name.isNotBlank() && name !in taken, onClose) {
|
CancelSaveRow(name.isNotBlank() && name !in taken && options.isNotEmpty() && default in options.indices, onClose) {
|
||||||
onSave(name, options)
|
onSave(name, options, default)
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) { focus.requestFocus() }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ModCatScaleDialog(
|
||||||
|
current: UiGradeType.Categoric, onClose: () -> Unit,
|
||||||
|
onSave: (UiGradeType.Categoric, List<String>, Int) -> Unit
|
||||||
|
) = DialogWindow(
|
||||||
|
onCloseRequest = onClose,
|
||||||
|
state = rememberDialogState(size = DpSize(750.dp, 600.dp), position = WindowPosition(Alignment.Center))
|
||||||
|
) {
|
||||||
|
val focus = remember { FocusRequester() }
|
||||||
|
val name = current.grade.name
|
||||||
|
var default by remember(current) {
|
||||||
|
mutableStateOf(maxOf(current.options.indexOfFirst { it.id.value == current.default?.id?.value }, 0))
|
||||||
|
}
|
||||||
|
var options by remember(current) { mutableStateOf(current.options) }
|
||||||
|
var added by remember(current) { mutableStateOf(listOf<String>()) }
|
||||||
|
var adding by remember(current) { mutableStateOf("") }
|
||||||
|
|
||||||
|
Surface(Modifier.fillMaxSize()) {
|
||||||
|
Box(Modifier.fillMaxSize().padding(10.dp)) {
|
||||||
|
Column(Modifier.align(Alignment.Center)) {
|
||||||
|
OutlinedTextField(name, {}, Modifier.fillMaxWidth(), label = { Text("Grading system name") }, singleLine = true, enabled = false)
|
||||||
|
Text("Grade options:", style = MaterialTheme.typography.headlineSmall, modifier = Modifier.padding(top = 10.dp))
|
||||||
|
LazyColumn(Modifier.weight(1f)) {
|
||||||
|
itemsIndexed(options) { idx, it ->
|
||||||
|
Row(Modifier.fillMaxWidth().padding(5.dp)) {
|
||||||
|
Column(Modifier.weight(1f).align(Alignment.CenterVertically)) {
|
||||||
|
Text(it.option)
|
||||||
|
if(idx == default) Text("(default option)", Modifier.padding(start = 10.dp), fontStyle = FontStyle.Italic)
|
||||||
|
}
|
||||||
|
IconButton({ default = idx }, Modifier.align(Alignment.CenterVertically)) {
|
||||||
|
if(default == idx) Icon(CheckboxChecked, "Default option")
|
||||||
|
else Icon(CheckboxUnchecked, "Set as default")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
itemsIndexed(added) { idx, it ->
|
||||||
|
Row(Modifier.fillMaxWidth().padding(5.dp)) {
|
||||||
|
Column(Modifier.weight(1f).align(Alignment.CenterVertically)) {
|
||||||
|
Text(it)
|
||||||
|
if(idx + options.size == default) Text("(default option)", Modifier.padding(start = 10.dp), fontStyle = FontStyle.Italic)
|
||||||
|
}
|
||||||
|
IconButton({ default = idx + options.size }, Modifier.align(Alignment.CenterVertically)) {
|
||||||
|
if(default == idx) Icon(CheckboxChecked, "Default option")
|
||||||
|
else Icon(CheckboxUnchecked, "Set as default")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Row {
|
||||||
|
OutlinedTextField(adding, { adding = it }, Modifier.weight(1f).align(Alignment.CenterVertically).padding(5.dp), label = { Text("New option") }, isError = adding in options.map { it.option } || adding in added, singleLine = true)
|
||||||
|
Button({ added = added + adding; adding = "" }, Modifier.align(Alignment.CenterVertically).padding(5.dp), enabled = adding.isNotBlank() && adding !in options.map { it.option } && adding !in added) {
|
||||||
|
Text("Add")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CancelSaveRow(true, onClose) {
|
||||||
|
onSave(current, added, default)
|
||||||
onClose()
|
onClose()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import androidx.compose.material3.Icon
|
|||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.LocalTextStyle
|
import androidx.compose.material3.LocalTextStyle
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.ProvideTextStyle
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@@ -48,6 +49,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
|||||||
import com.jaytux.grader.data.v2.Group
|
import com.jaytux.grader.data.v2.Group
|
||||||
import com.jaytux.grader.data.v2.Student
|
import com.jaytux.grader.data.v2.Student
|
||||||
import com.jaytux.grader.startEmail
|
import com.jaytux.grader.startEmail
|
||||||
|
import com.jaytux.grader.ui.EditableText
|
||||||
import com.jaytux.grader.viewmodel.EditionVM
|
import com.jaytux.grader.viewmodel.EditionVM
|
||||||
import com.jaytux.grader.viewmodel.SnackVM
|
import com.jaytux.grader.viewmodel.SnackVM
|
||||||
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
|
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
|
||||||
@@ -63,8 +65,10 @@ fun GroupsView(vm: EditionVM) = Row(Modifier.fillMaxSize()) {
|
|||||||
var swappingRole by remember { mutableStateOf(-1) }
|
var swappingRole by remember { mutableStateOf(-1) }
|
||||||
|
|
||||||
val group = remember(groups, focus) { if(focus != -1) groups[focus] else null }
|
val group = remember(groups, focus) { if(focus != -1) groups[focus] else null }
|
||||||
|
val groupNames = remember(groups) { groups.map { it.group.name } }
|
||||||
val grades by vm.groupGrades.entities
|
val grades by vm.groupGrades.entities
|
||||||
val snacks = viewModel<SnackVM> { SnackVM() }
|
val snacks = viewModel<SnackVM> { SnackVM() }
|
||||||
|
var deleting by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
Surface(Modifier.weight(0.25f).fillMaxHeight(), tonalElevation = 7.dp) {
|
Surface(Modifier.weight(0.25f).fillMaxHeight(), tonalElevation = 7.dp) {
|
||||||
ListOrEmpty(groups, { Text("No groups yet.") }) { idx, it ->
|
ListOrEmpty(groups, { Text("No groups yet.") }) { idx, it ->
|
||||||
@@ -80,14 +84,21 @@ fun GroupsView(vm: EditionVM) = Row(Modifier.fillMaxSize()) {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Column(Modifier.padding(10.dp)) {
|
Column(Modifier.padding(10.dp)) {
|
||||||
Row(Modifier.height(IntrinsicSize.Min), verticalAlignment = Alignment.CenterVertically) {
|
EditableName(
|
||||||
Text(group.group.name, style = MaterialTheme.typography.headlineMedium)
|
group.group.name, groupNames,
|
||||||
|
{ vm.modGroup(group.group, it) },
|
||||||
|
{ deleting = true },
|
||||||
|
style = MaterialTheme.typography.headlineMedium
|
||||||
|
) {
|
||||||
if (group.members.any { it.first.contact.isNotBlank() }) {
|
if (group.members.any { it.first.contact.isNotBlank() }) {
|
||||||
IconButton({ startEmail(group.members.mapNotNull { it.first.contact.ifBlank { null } }) { snacks.show(it) } }) {
|
IconButton(Mail, "Send email", Modifier.align(Alignment.CenterVertically)) {
|
||||||
Icon(Mail, "Send email", Modifier.fillMaxHeight())
|
startEmail(group.members.mapNotNull { it.first.contact.ifBlank { null } }) {
|
||||||
|
snacks.show(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(Modifier.height(5.dp))
|
Spacer(Modifier.height(5.dp))
|
||||||
Row(Modifier.padding(5.dp)) {
|
Row(Modifier.padding(5.dp)) {
|
||||||
var showTargetBorder by remember { mutableStateOf(false) }
|
var showTargetBorder by remember { mutableStateOf(false) }
|
||||||
@@ -224,6 +235,15 @@ fun GroupsView(vm: EditionVM) = Row(Modifier.fillMaxSize()) {
|
|||||||
swappingRole = -1
|
swappingRole = -1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(deleting) {
|
||||||
|
if(group == null) deleting = false
|
||||||
|
else {
|
||||||
|
ConfirmDeleteDialog("a group", { deleting = false }, { vm.rmGroup(group.group) }) {
|
||||||
|
Text(group.group.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DDTarget<T>(val onStart: () -> Unit, val onEnd: () -> Unit, val validator: (Transferable) -> T?, val handle: (T) -> Unit) : DragAndDropTarget {
|
private class DDTarget<T>(val onStart: () -> Unit, val onEnd: () -> Unit, val validator: (Transferable) -> T?, val handle: (T) -> Unit) : DragAndDropTarget {
|
||||||
|
|||||||
@@ -1430,3 +1430,84 @@ val Mail: ImageVector by lazy {
|
|||||||
}
|
}
|
||||||
}.build()
|
}.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val CheckboxUnchecked: ImageVector by lazy {
|
||||||
|
ImageVector.Builder(
|
||||||
|
name = "checkbox-unchecked",
|
||||||
|
defaultWidth = 24.dp,
|
||||||
|
defaultHeight = 24.dp,
|
||||||
|
viewportWidth = 24f,
|
||||||
|
viewportHeight = 24f
|
||||||
|
).apply {
|
||||||
|
path(
|
||||||
|
fill = SolidColor(Color.Black)
|
||||||
|
) {
|
||||||
|
moveTo(3f, 6.25f)
|
||||||
|
curveTo(3f, 4.45507f, 4.45507f, 3f, 6.25f, 3f)
|
||||||
|
horizontalLineTo(17.75f)
|
||||||
|
curveTo(19.5449f, 3f, 21f, 4.45507f, 21f, 6.25f)
|
||||||
|
verticalLineTo(17.75f)
|
||||||
|
curveTo(21f, 19.5449f, 19.5449f, 21f, 17.75f, 21f)
|
||||||
|
horizontalLineTo(6.25f)
|
||||||
|
curveTo(4.45507f, 21f, 3f, 19.5449f, 3f, 17.75f)
|
||||||
|
verticalLineTo(6.25f)
|
||||||
|
close()
|
||||||
|
moveTo(6.25f, 4.5f)
|
||||||
|
curveTo(5.2835f, 4.5f, 4.5f, 5.2835f, 4.5f, 6.25f)
|
||||||
|
verticalLineTo(17.75f)
|
||||||
|
curveTo(4.5f, 18.7165f, 5.2835f, 19.5f, 6.25f, 19.5f)
|
||||||
|
horizontalLineTo(17.75f)
|
||||||
|
curveTo(18.7165f, 19.5f, 19.5f, 18.7165f, 19.5f, 17.75f)
|
||||||
|
verticalLineTo(6.25f)
|
||||||
|
curveTo(19.5f, 5.2835f, 18.7165f, 4.5f, 17.75f, 4.5f)
|
||||||
|
horizontalLineTo(6.25f)
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
}.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
val CheckboxChecked: ImageVector by lazy {
|
||||||
|
ImageVector.Builder(
|
||||||
|
name = "checkbox-checked",
|
||||||
|
defaultWidth = 24.dp,
|
||||||
|
defaultHeight = 24.dp,
|
||||||
|
viewportWidth = 24f,
|
||||||
|
viewportHeight = 24f
|
||||||
|
).apply {
|
||||||
|
path(
|
||||||
|
fill = SolidColor(Color.Black)
|
||||||
|
) {
|
||||||
|
moveTo(6.25f, 3f)
|
||||||
|
curveTo(4.45507f, 3f, 3f, 4.45507f, 3f, 6.25f)
|
||||||
|
verticalLineTo(17.75f)
|
||||||
|
curveTo(3f, 19.5449f, 4.45507f, 21f, 6.25f, 21f)
|
||||||
|
horizontalLineTo(17.75f)
|
||||||
|
curveTo(19.5449f, 21f, 21f, 19.5449f, 21f, 17.75f)
|
||||||
|
verticalLineTo(6.25f)
|
||||||
|
curveTo(21f, 4.45507f, 19.5449f, 3f, 17.75f, 3f)
|
||||||
|
horizontalLineTo(6.25f)
|
||||||
|
close()
|
||||||
|
moveTo(4.5f, 6.25f)
|
||||||
|
curveTo(4.5f, 5.2835f, 5.2835f, 4.5f, 6.25f, 4.5f)
|
||||||
|
horizontalLineTo(17.75f)
|
||||||
|
curveTo(18.7165f, 4.5f, 19.5f, 5.2835f, 19.5f, 6.25f)
|
||||||
|
verticalLineTo(17.75f)
|
||||||
|
curveTo(19.5f, 18.7165f, 18.7165f, 19.5f, 17.75f, 19.5f)
|
||||||
|
horizontalLineTo(6.25f)
|
||||||
|
curveTo(5.2835f, 19.5f, 4.5f, 18.7165f, 4.5f, 17.75f)
|
||||||
|
verticalLineTo(6.25f)
|
||||||
|
close()
|
||||||
|
moveTo(17.28f, 9.28064f)
|
||||||
|
curveTo(17.5731f, 8.98791f, 17.5734f, 8.51304f, 17.2806f, 8.21998f)
|
||||||
|
curveTo(16.9879f, 7.92691f, 16.513f, 7.92664f, 16.22f, 8.21936f)
|
||||||
|
lineTo(9.99658f, 14.4356f)
|
||||||
|
lineTo(7.78084f, 12.2197f)
|
||||||
|
curveTo(7.48795f, 11.9268f, 7.01308f, 11.9268f, 6.72018f, 12.2196f)
|
||||||
|
curveTo(6.42727f, 12.5125f, 6.42726f, 12.9874f, 6.72014f, 13.2803f)
|
||||||
|
lineTo(9.46591f, 16.0262f)
|
||||||
|
curveTo(9.75868f, 16.319f, 10.2333f, 16.3192f, 10.5263f, 16.0266f)
|
||||||
|
lineTo(17.28f, 9.28064f)
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
}.build()
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.material.Divider
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
@@ -48,6 +49,7 @@ fun StudentsView(vm: EditionVM) = Row(Modifier.fillMaxSize()) {
|
|||||||
val students by vm.studentList.entities
|
val students by vm.studentList.entities
|
||||||
val focus by vm.focusIndex
|
val focus by vm.focusIndex
|
||||||
val snacks = viewModel<SnackVM> { SnackVM() }
|
val snacks = viewModel<SnackVM> { SnackVM() }
|
||||||
|
var deleting by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
Surface(Modifier.weight(0.25f).fillMaxHeight(), tonalElevation = 7.dp) {
|
Surface(Modifier.weight(0.25f).fillMaxHeight(), tonalElevation = 7.dp) {
|
||||||
ListOrEmpty(students, { Text("No students yet.") }) { idx, it ->
|
ListOrEmpty(students, { Text("No students yet.") }) { idx, it ->
|
||||||
@@ -69,42 +71,25 @@ fun StudentsView(vm: EditionVM) = Row(Modifier.fillMaxSize()) {
|
|||||||
Surface(Modifier.padding(10.dp).fillMaxWidth(), tonalElevation = 10.dp, shadowElevation = 2.dp, shape = MaterialTheme.shapes.medium) {
|
Surface(Modifier.padding(10.dp).fillMaxWidth(), tonalElevation = 10.dp, shadowElevation = 2.dp, shape = MaterialTheme.shapes.medium) {
|
||||||
Column(Modifier.padding(10.dp)) {
|
Column(Modifier.padding(10.dp)) {
|
||||||
Row(Modifier.height(IntrinsicSize.Min), verticalAlignment = Alignment.CenterVertically) {
|
Row(Modifier.height(IntrinsicSize.Min), verticalAlignment = Alignment.CenterVertically) {
|
||||||
Text(students[focus].name, style = MaterialTheme.typography.headlineSmall)
|
EditableText(
|
||||||
if(students[focus].contact.isNotBlank()) {
|
students[focus].name, style = MaterialTheme.typography.headlineSmall,
|
||||||
IconButton({ startEmail(listOf(students[focus].contact)) { snacks.show(it) } }) {
|
canSave = { it.isNotBlank() && (it == students[focus].name || !students.any { x -> x.name == it }) }
|
||||||
Icon(Mail, "Send email", Modifier.fillMaxHeight())
|
) {
|
||||||
}
|
vm.modStudent(students[focus], it, null, null)
|
||||||
|
}
|
||||||
|
Spacer(Modifier.width(10.dp))
|
||||||
|
IconButton(Delete, "Delete student", Modifier.align(Alignment.CenterVertically)) {
|
||||||
|
deleting = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Row {
|
Row {
|
||||||
var editing by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
Text("Contact: ", Modifier.align(Alignment.CenterVertically).padding(start = 15.dp))
|
Text("Contact: ", Modifier.align(Alignment.CenterVertically).padding(start = 15.dp))
|
||||||
if(!editing) {
|
|
||||||
if (students[focus].contact.isBlank()) {
|
EditableText(students[focus].contact, Modifier.align(Alignment.CenterVertically), displayAdapt = { it.ifBlank { "No contact info." } }) {
|
||||||
Text(
|
vm.modStudent(students[focus], null, it, null)
|
||||||
"No contact info.",
|
|
||||||
Modifier.padding(start = 5.dp),
|
|
||||||
fontStyle = FontStyle.Italic,
|
|
||||||
color = LocalTextStyle.current.color.copy(alpha = 0.5f)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Text(students[focus].contact, Modifier.padding(start = 5.dp))
|
|
||||||
}
|
|
||||||
Spacer(Modifier.width(5.dp))
|
|
||||||
Icon(Edit, "Edit contact info", Modifier.clickable { editing = true })
|
|
||||||
}
|
}
|
||||||
else {
|
IconButton(Mail, "Send email") {
|
||||||
var mod by remember(focus, students[focus].contact, students[focus].id.value) { mutableStateOf(students[focus].contact) }
|
startEmail(listOf(students[focus].contact)) { snacks.show(it) }
|
||||||
OutlinedTextField(mod, { mod = it })
|
|
||||||
Spacer(Modifier.width(5.dp))
|
|
||||||
Icon(Check, "Confirm edit", Modifier.align(Alignment.CenterVertically).clickable {
|
|
||||||
vm.modStudent(students[focus], null, mod, null)
|
|
||||||
editing = false
|
|
||||||
})
|
|
||||||
Spacer(Modifier.width(5.dp))
|
|
||||||
Icon(Close, "Cancel edit", Modifier.align(Alignment.CenterVertically).clickable { editing = false })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,6 +148,9 @@ fun StudentsView(vm: EditionVM) = Row(Modifier.fillMaxSize()) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
items(grades ?: listOf()) {
|
items(grades ?: listOf()) {
|
||||||
|
Column(Modifier.padding(10.dp)) {
|
||||||
|
Divider()
|
||||||
|
}
|
||||||
Column(Modifier.padding(10.dp)) {
|
Column(Modifier.padding(10.dp)) {
|
||||||
Row {
|
Row {
|
||||||
Text(it.assignment.name, Modifier.weight(0.66f))
|
Text(it.assignment.name, Modifier.weight(0.66f))
|
||||||
@@ -193,6 +181,15 @@ fun StudentsView(vm: EditionVM) = Row(Modifier.fillMaxSize()) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(deleting) {
|
||||||
|
if(focus == -1) deleting = false
|
||||||
|
else {
|
||||||
|
ConfirmDeleteDialog("a student", { deleting = false }, { vm.rmStudent(students[focus]) }) {
|
||||||
|
Text(students[focus].name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
@@ -1,200 +0,0 @@
|
|||||||
package com.jaytux.grader.ui
|
|
||||||
|
|
||||||
|
|
||||||
//@Composable
|
|
||||||
//fun StudentView(state: StudentState, nav: Navigators) {
|
|
||||||
// val groups by state.groups.entities
|
|
||||||
// val courses by state.courseEditions.entities
|
|
||||||
// val groupGrades by state.groupGrades.entities
|
|
||||||
// val soloGrades by state.soloGrades.entities
|
|
||||||
//
|
|
||||||
// Column(Modifier.padding(10.dp)) {
|
|
||||||
// Row {
|
|
||||||
// Column(Modifier.weight(0.45f)) {
|
|
||||||
// Column(Modifier.padding(10.dp).weight(0.35f)) {
|
|
||||||
// Spacer(Modifier.height(10.dp))
|
|
||||||
// InteractToEdit(state.student.name, { state.update { this.name = it } }, "Name")
|
|
||||||
// InteractToEdit(state.student.contact, { state.update { this.contact = it } }, "Contact")
|
|
||||||
// InteractToEdit(state.student.note, { state.update { this.note = it } }, "Note", singleLine = false)
|
|
||||||
// }
|
|
||||||
// Column(Modifier.weight(0.20f)) {
|
|
||||||
// Text("Courses", style = MaterialTheme.typography.headlineSmall)
|
|
||||||
// ListOrEmpty(courses, { Text("Not a member of any course") }) { _, it ->
|
|
||||||
// val (ed, course) = it
|
|
||||||
// Text("${course.name} (${ed.name})", style = MaterialTheme.typography.bodyMedium)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// Column(Modifier.weight(0.45f)) {
|
|
||||||
// Text("Groups", style = MaterialTheme.typography.headlineSmall)
|
|
||||||
// ListOrEmpty(groups, { Text("Not a member of any group") }) { _, it ->
|
|
||||||
// val (group, c) = it
|
|
||||||
// val (course, ed) = c
|
|
||||||
// Row(Modifier.clickable { nav.group(group) }) {
|
|
||||||
// Text(group.name, style = MaterialTheme.typography.bodyMedium)
|
|
||||||
// Spacer(Modifier.width(5.dp))
|
|
||||||
// Text(
|
|
||||||
// "(in course $course ($ed))",
|
|
||||||
// Modifier.align(Alignment.Bottom),
|
|
||||||
// style = MaterialTheme.typography.bodySmall
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// Column(Modifier.weight(0.55f)) {
|
|
||||||
// Text("Courses", style = MaterialTheme.typography.headlineSmall)
|
|
||||||
// LazyColumn {
|
|
||||||
// item {
|
|
||||||
// Text("As group member", fontWeight = FontWeight.Bold)
|
|
||||||
// }
|
|
||||||
// items(groupGrades) {
|
|
||||||
// groupGradeWidget(it)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// item {
|
|
||||||
// Text("Solo assignments", fontWeight = FontWeight.Bold)
|
|
||||||
// }
|
|
||||||
// items(soloGrades) {
|
|
||||||
// soloGradeWidget(it)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//@Composable
|
|
||||||
//fun groupGradeWidget(gg: StudentState.LocalGroupGrade) {
|
|
||||||
// val (group, assignment, gGrade, iGrade) = gg
|
|
||||||
// var expanded by remember { mutableStateOf(false) }
|
|
||||||
// Row(Modifier.padding(5.dp)) {
|
|
||||||
// Spacer(Modifier.width(10.dp))
|
|
||||||
// Surface(
|
|
||||||
// Modifier.clickable { expanded = !expanded }.fillMaxWidth(),
|
|
||||||
// tonalElevation = 5.dp,
|
|
||||||
// shape = MaterialTheme.shapes.medium
|
|
||||||
// ) {
|
|
||||||
// Column(Modifier.padding(5.dp)) {
|
|
||||||
// Text("${assignment.maxN(25)} (${iGrade ?: gGrade ?: "no grade yet"})")
|
|
||||||
//
|
|
||||||
// if (expanded) {
|
|
||||||
// Row {
|
|
||||||
// Spacer(Modifier.width(10.dp))
|
|
||||||
// Column {
|
|
||||||
// ItalicAndNormal("Assignment: ", assignment)
|
|
||||||
// ItalicAndNormal("Group name: ", group)
|
|
||||||
// ItalicAndNormal("Group grade: ", gGrade ?: "no grade yet")
|
|
||||||
// ItalicAndNormal("Individual grade: ", iGrade ?: "no individual grade")
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//@Composable
|
|
||||||
//fun soloGradeWidget(sg: StudentState.LocalSoloGrade) {
|
|
||||||
// val (assignment, grade) = sg
|
|
||||||
// var expanded by remember { mutableStateOf(false) }
|
|
||||||
// Row(Modifier.padding(5.dp)) {
|
|
||||||
// Spacer(Modifier.width(10.dp))
|
|
||||||
// Surface(
|
|
||||||
// Modifier.clickable { expanded = !expanded }.fillMaxWidth(),
|
|
||||||
// tonalElevation = 5.dp,
|
|
||||||
// shape = MaterialTheme.shapes.medium
|
|
||||||
// ) {
|
|
||||||
// Column(Modifier.padding(5.dp)) {
|
|
||||||
// Text("${assignment.maxN(25)} (${grade ?: "no grade yet"})")
|
|
||||||
//
|
|
||||||
// if (expanded) {
|
|
||||||
// Row {
|
|
||||||
// Spacer(Modifier.width(10.dp))
|
|
||||||
// Column {
|
|
||||||
// ItalicAndNormal("Assignment: ", assignment)
|
|
||||||
// ItalicAndNormal("Individual grade: ", grade ?: "no grade yet")
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//@Composable
|
|
||||||
//fun GroupView(state: GroupState, nav: Navigators) {
|
|
||||||
// val members by state.members.entities
|
|
||||||
// val available by state.availableStudents.entities
|
|
||||||
// val allRoles by state.roles.entities
|
|
||||||
//
|
|
||||||
// var pickRole: Pair<String?, (String?) -> Unit>? by remember { mutableStateOf(null) }
|
|
||||||
//
|
|
||||||
// Column(Modifier.padding(10.dp)) {
|
|
||||||
// Row {
|
|
||||||
// Column(Modifier.weight(0.5f)) {
|
|
||||||
// Text("Students", style = MaterialTheme.typography.headlineSmall)
|
|
||||||
// ListOrEmpty(members, { Text("No students in this group") }) { _, it ->
|
|
||||||
// val (student, role) = it
|
|
||||||
// Row(Modifier.clickable { nav.student(student) }) {
|
|
||||||
// Text(
|
|
||||||
// "${student.name} (${role ?: "no role"})",
|
|
||||||
// Modifier.weight(0.75f).align(Alignment.CenterVertically),
|
|
||||||
// style = MaterialTheme.typography.bodyMedium
|
|
||||||
// )
|
|
||||||
// IconButton({ pickRole = role to { r -> state.updateRole(student, r) } }, Modifier.weight(0.12f)) {
|
|
||||||
// Icon(Icons.Default.Edit, "Change role")
|
|
||||||
// }
|
|
||||||
// IconButton({ state.removeStudent(student) }, Modifier.weight(0.12f)) {
|
|
||||||
// Icon(Icons.Default.Delete, "Remove student")
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// Column(Modifier.weight(0.5f)) {
|
|
||||||
// Text("Available students", style = MaterialTheme.typography.headlineSmall)
|
|
||||||
// ListOrEmpty(available, { Text("No students available") }) { _, it ->
|
|
||||||
// Row(Modifier.padding(5.dp).clickable { nav.student(it) }) {
|
|
||||||
// IconButton({ state.addStudent(it) }) {
|
|
||||||
// Icon(ChevronLeft, "Add student")
|
|
||||||
// }
|
|
||||||
// Text(it.name, Modifier.weight(0.75f).align(Alignment.CenterVertically), style = MaterialTheme.typography.bodyMedium)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// pickRole?.let {
|
|
||||||
// val (curr, onPick) = it
|
|
||||||
// RolePicker(allRoles, curr, { pickRole = null }, { role -> onPick(role); pickRole = null })
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//@Composable
|
|
||||||
//fun RolePicker(used: List<String>, curr: String?, onClose: () -> Unit, onSave: (String?) -> Unit) = DialogWindow(
|
|
||||||
// onCloseRequest = onClose,
|
|
||||||
// state = rememberDialogState(size = DpSize(400.dp, 500.dp), position = WindowPosition(Alignment.Center))
|
|
||||||
//) {
|
|
||||||
// Surface(Modifier.fillMaxSize().padding(10.dp)) {
|
|
||||||
// Box(Modifier.fillMaxSize()) {
|
|
||||||
// var role by remember { mutableStateOf(curr ?: "") }
|
|
||||||
// Column {
|
|
||||||
// Text("Used roles:")
|
|
||||||
// LazyColumn(Modifier.weight(1.0f).padding(5.dp)) {
|
|
||||||
// items(used) {
|
|
||||||
// Surface(Modifier.fillMaxWidth().clickable { role = it }, tonalElevation = 5.dp) {
|
|
||||||
// Text(it, Modifier.padding(5.dp))
|
|
||||||
// }
|
|
||||||
// Spacer(Modifier.height(5.dp))
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// OutlinedTextField(role, { role = it }, Modifier.fillMaxWidth())
|
|
||||||
// CancelSaveRow(true, onClose) {
|
|
||||||
// onSave(role.ifBlank { null })
|
|
||||||
// onClose()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
@@ -7,6 +7,9 @@ import androidx.compose.foundation.lazy.LazyColumn
|
|||||||
import androidx.compose.foundation.lazy.LazyItemScope
|
import androidx.compose.foundation.lazy.LazyItemScope
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
|
import androidx.compose.material.ContentAlpha
|
||||||
|
import androidx.compose.material.LocalTextStyle
|
||||||
|
import androidx.compose.material.ProvideTextStyle
|
||||||
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
|
||||||
@@ -16,12 +19,15 @@ import androidx.compose.ui.focus.focusRequester
|
|||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
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.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.layout.onGloballyPositioned
|
import androidx.compose.ui.layout.onGloballyPositioned
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.font.FontStyle
|
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.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.DpSize
|
import androidx.compose.ui.unit.DpSize
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.isUnspecified
|
||||||
import androidx.compose.ui.window.*
|
import androidx.compose.ui.window.*
|
||||||
import com.jaytux.grader.maxN
|
import com.jaytux.grader.maxN
|
||||||
import com.jaytux.grader.viewmodel.Grade
|
import com.jaytux.grader.viewmodel.Grade
|
||||||
@@ -275,3 +281,65 @@ fun GradePicker(grade: Grade, modifier: Modifier = Modifier, key: Any = Unit, on
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun EditableText(
|
||||||
|
text: String, modifier: Modifier = Modifier, key: Any = Unit, style: TextStyle = LocalTextStyle.current,
|
||||||
|
label: (@Composable () -> Unit)? = null, displayAdapt: ((String) -> String)? = null,
|
||||||
|
canSave: (String) -> Boolean = { true },
|
||||||
|
onUpdate: (String) -> Unit
|
||||||
|
) {
|
||||||
|
var editing by remember(text, key) { mutableStateOf(false) }
|
||||||
|
|
||||||
|
if(editing) {
|
||||||
|
var current by remember(text, key) { mutableStateOf(text) }
|
||||||
|
val enableSave = canSave(current)
|
||||||
|
|
||||||
|
Row(modifier) {
|
||||||
|
OutlinedTextField(current, { current = it }, Modifier.align(Alignment.CenterVertically), label = label, textStyle = style, isError = !enableSave)
|
||||||
|
Spacer(Modifier.width(5.dp))
|
||||||
|
IconButton(Check, "Confirm edit", Modifier.align(Alignment.CenterVertically), enableSave, iconHeight = style.fontSize.toDp()) {
|
||||||
|
onUpdate(current)
|
||||||
|
editing = false
|
||||||
|
}
|
||||||
|
Spacer(Modifier.width(5.dp))
|
||||||
|
IconButton(Close, "Cancel edit", Modifier.align(Alignment.CenterVertically), iconHeight = style.fontSize.toDp()) {
|
||||||
|
editing = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Row(modifier) {
|
||||||
|
Text(displayAdapt?.let { it(text) } ?: text, Modifier.align(Alignment.CenterVertically), style = style)
|
||||||
|
Spacer(Modifier.width(5.dp))
|
||||||
|
IconButton(Edit, "Edit", iconHeight = style.fontSize.toDp()) {
|
||||||
|
editing = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun IconButton(icon: ImageVector, contentDescription: String? = null, modifier: Modifier = Modifier, enabled: Boolean = true, iconHeight: Dp = Dp.Unspecified, onClick: () -> Unit) =
|
||||||
|
IconButton(onClick, modifier, enabled) {
|
||||||
|
if(iconHeight.isUnspecified) Icon(icon, contentDescription, modifier = Modifier.height(LocalTextStyle.current.fontSize.toDp()))
|
||||||
|
else Icon(icon, contentDescription, modifier = Modifier.height(iconHeight))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun EditableName(
|
||||||
|
name: String, taken: List<String>, onUpdate: (String) -> Unit, onDelete: () -> Unit, modifier: Modifier = Modifier,
|
||||||
|
style: TextStyle = LocalTextStyle.current, displayAdapt: ((String) -> String)? = null,
|
||||||
|
addBeforeDelete: (@Composable RowScope.() -> Unit)? = null
|
||||||
|
) = Row(modifier) {
|
||||||
|
ProvideTextStyle(style) {
|
||||||
|
EditableText(
|
||||||
|
name, style = style, canSave = { it.isNotBlank() && (it == name || it !in taken) }, onUpdate = onUpdate,
|
||||||
|
displayAdapt = displayAdapt
|
||||||
|
)
|
||||||
|
addBeforeDelete?.invoke(this@Row)
|
||||||
|
IconButton(Delete, "Delete", Modifier.align(Alignment.CenterVertically)) {
|
||||||
|
onDelete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -57,7 +57,7 @@ class EditionVM(val edition: Edition, val course: Course) : ViewModel() {
|
|||||||
|
|
||||||
val categoricGrades = RawDbState {
|
val categoricGrades = RawDbState {
|
||||||
CategoricGrade.all().map {
|
CategoricGrade.all().map {
|
||||||
UiGradeType.Categoric(it.options.toList(), it)
|
UiGradeType.Categoric(it.options.toList(), it.default, it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,24 +66,35 @@ class EditionVM(val edition: Edition, val course: Course) : ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val studentGrades = RawDbFocusableState { st: Student ->
|
val studentGrades = RawDbFocusableState { st: Student ->
|
||||||
|
println("Loading grade summary for student ${st.name}")
|
||||||
val groupIds = st.groups.map { it.group.id }.toSet()
|
val groupIds = st.groups.map { it.group.id }.toSet()
|
||||||
|
|
||||||
edition.assignments.map { asg ->
|
edition.assignments.map { asg ->
|
||||||
val (grade, memberOf, override) = when(asg.type) {
|
val (grade, memberOf, override) = when(asg.type) {
|
||||||
AssignmentType.GROUP -> {
|
AssignmentType.GROUP -> {
|
||||||
val asGroup = asg.globalCriterion.feedbacks.find { it.asGroupFeedback?.id in groupIds }
|
val (asGroup, raw) = asg.globalCriterion.feedbacks.find { it.asGroupFeedback?.id in groupIds }?.let { Grade.fromAssignment(asg.globalCriterion, it) to it } ?: (null to null)
|
||||||
val solo = asg.globalCriterion.feedbacks.find { it.forStudentsOverrideIfGroup.any { over -> over.student == st } }
|
val solo = run findSolo@{
|
||||||
val gr = (solo ?: asGroup)?.let { Grade.fromAssignment(asg.globalCriterion, it) }
|
for(groupLevel in asg.globalCriterion.feedbacks) {
|
||||||
gr to asGroup?.asGroupFeedback app (solo != null)
|
for(override in groupLevel.forStudentsOverrideIfGroup) {
|
||||||
|
if(override.student.id == st.id) return@findSolo Grade.fromAssignment(asg.globalCriterion, override.feedback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
null
|
||||||
|
}
|
||||||
|
val gr = (solo ?: asGroup)//?.let { Grade.fromAssignment(asg.globalCriterion, it) }
|
||||||
|
println(" -> For group assignment ${asg.name}: $gr (solo override: $solo, group feedback: $asGroup)")
|
||||||
|
gr to raw?.asGroupFeedback app (solo != null)
|
||||||
}
|
}
|
||||||
AssignmentType.SOLO -> {
|
AssignmentType.SOLO -> {
|
||||||
val eval = asg.globalCriterion.feedbacks.find { it.asSoloFeedback == st }
|
val eval = asg.globalCriterion.feedbacks.find { it.asSoloFeedback == st }
|
||||||
?.let { Grade.fromAssignment(asg.globalCriterion, it) }
|
?.let { Grade.fromAssignment(asg.globalCriterion, it) }
|
||||||
|
println(" -> For solo assignment ${asg.name}: $eval")
|
||||||
eval to null app false
|
eval to null app false
|
||||||
}
|
}
|
||||||
AssignmentType.PEER_EVALUATION -> {
|
AssignmentType.PEER_EVALUATION -> {
|
||||||
val eval = asg.globalCriterion.feedbacks.find { it.asPeerEvaluationFeedback?.id == st.id }
|
val eval = asg.globalCriterion.feedbacks.find { it.asPeerEvaluationFeedback?.id == st.id }
|
||||||
?.let { Grade.fromAssignment(asg.globalCriterion, it) }
|
?.let { Grade.fromAssignment(asg.globalCriterion, it) }
|
||||||
|
println(" -> For peer evaluation assignment ${asg.name}: $eval")
|
||||||
eval to null app false
|
eval to null app false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -129,6 +140,43 @@ class EditionVM(val edition: Edition, val course: Course) : ViewModel() {
|
|||||||
val selectedTab = _selectedTab.immutable()
|
val selectedTab = _selectedTab.immutable()
|
||||||
val focusIndex = _focusIndex.immutable()
|
val focusIndex = _focusIndex.immutable()
|
||||||
|
|
||||||
|
init {
|
||||||
|
transaction {
|
||||||
|
var count0 = 0
|
||||||
|
StudentOverrideFeedback.all().forEach {
|
||||||
|
val group = it.group.name
|
||||||
|
val student = it.student.name
|
||||||
|
val assignment = it.feedback.criterion
|
||||||
|
val assName = assignment.assignment.name
|
||||||
|
val ogGrade = Grade.fromAssignment(assignment, it.overrides)
|
||||||
|
val updGrade = Grade.fromAssignment(assignment, it.feedback)
|
||||||
|
println("OVERRIDE: '$student' in '$group' for '$assName' ('${assignment.name}'): $ogGrade -> $updGrade")
|
||||||
|
count0++
|
||||||
|
}
|
||||||
|
println(" --> Direct lookup: $count0 overrides")
|
||||||
|
|
||||||
|
var count1 = 0
|
||||||
|
GroupAssignment.all().forEach { asg ->
|
||||||
|
val assignment = asg.base
|
||||||
|
val baseCrit = assignment.globalCriterion
|
||||||
|
val overrides = baseCrit.feedbacks.flatMap { it.forStudentsOverrideIfGroup }
|
||||||
|
if(overrides.isNotEmpty()) {
|
||||||
|
println("Assignment '${assignment.name}' has ${overrides.size} overrides:")
|
||||||
|
overrides.forEach {
|
||||||
|
val group = it.group.name
|
||||||
|
val student = it.student.name
|
||||||
|
val assName = assignment.name
|
||||||
|
val ogGrade = Grade.fromAssignment(baseCrit, it.overrides)
|
||||||
|
val updGrade = Grade.fromAssignment(baseCrit, it.feedback)
|
||||||
|
println(" - OVERRIDE: '$student' in '$group' for '$assName': $ogGrade -> $updGrade")
|
||||||
|
count1++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println(" --> GroupAssignment lookup: $count1 overrides")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun switchTo(tab: Tab) {
|
fun switchTo(tab: Tab) {
|
||||||
_selectedTab.value = tab
|
_selectedTab.value = tab
|
||||||
_focusIndex.value = -1
|
_focusIndex.value = -1
|
||||||
@@ -397,20 +445,38 @@ class EditionVM(val edition: Edition, val course: Course) : ViewModel() {
|
|||||||
assignmentList.refresh()
|
assignmentList.refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun mkScale(name: String, options: List<String>) {
|
fun mkScale(name: String, options: List<String>, default: Int) {
|
||||||
transaction {
|
transaction {
|
||||||
val grade = CategoricGrade.new { this.name = name }
|
val grade = CategoricGrade.new { this.name = name }
|
||||||
options.forEachIndexed { idx, opt ->
|
options.forEachIndexed { idx, opt ->
|
||||||
CategoricGradeOption.new {
|
val x = CategoricGradeOption.new {
|
||||||
this.grade = grade
|
this.grade = grade
|
||||||
this.option = opt
|
this.option = opt
|
||||||
this.index = idx
|
this.index = idx
|
||||||
}
|
}
|
||||||
|
if(idx == default) grade.default = x
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
categoricGrades.refresh()
|
categoricGrades.refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun modScale(grade: CategoricGrade, add: List<String>, default: Int) {
|
||||||
|
transaction {
|
||||||
|
val currMax = grade.options.maxOfOrNull { it.index } ?: 0
|
||||||
|
add.forEachIndexed { idx, opt ->
|
||||||
|
CategoricGradeOption.new {
|
||||||
|
this.grade = grade
|
||||||
|
this.option = opt
|
||||||
|
this.index = idx + currMax
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val default = grade.options.first { it.index == default }
|
||||||
|
grade.default = default
|
||||||
|
}
|
||||||
|
categoricGrades.refresh()
|
||||||
|
}
|
||||||
|
|
||||||
fun mkNumericScale(name: String, max: Double) {
|
fun mkNumericScale(name: String, max: Double) {
|
||||||
transaction {
|
transaction {
|
||||||
NumericGrade.new {
|
NumericGrade.new {
|
||||||
@@ -441,8 +507,14 @@ class EditionVM(val edition: Edition, val course: Course) : ViewModel() {
|
|||||||
|
|
||||||
fun rmAssignment(assignment: BaseAssignment) {
|
fun rmAssignment(assignment: BaseAssignment) {
|
||||||
transaction {
|
transaction {
|
||||||
assignment.delete()
|
assignment.criteria.forEach {
|
||||||
|
it.feedbacks.forEach { f ->
|
||||||
|
f.delete()
|
||||||
|
}
|
||||||
|
it.delete()
|
||||||
|
}
|
||||||
(assignment.asPeerEvaluation ?: assignment.asGroupAssignment ?: assignment.asSoloAssignment)?.delete()
|
(assignment.asPeerEvaluation ?: assignment.asGroupAssignment ?: assignment.asSoloAssignment)?.delete()
|
||||||
|
assignment.delete()
|
||||||
}
|
}
|
||||||
unfocus()
|
unfocus()
|
||||||
assignmentList.refresh()
|
assignmentList.refresh()
|
||||||
|
|||||||
@@ -39,10 +39,15 @@ sealed class Grade {
|
|||||||
companion object {
|
companion object {
|
||||||
context(trns: Transaction)
|
context(trns: Transaction)
|
||||||
fun fromAssignment(asg: Criterion, fdb: BaseFeedback): Grade = when(asg.gradeType) {
|
fun fromAssignment(asg: Criterion, fdb: BaseFeedback): Grade = when(asg.gradeType) {
|
||||||
GradeType.CATEGORIC ->
|
GradeType.CATEGORIC -> {
|
||||||
Categoric(fdb.gradeCategoric!!, asg.categoricGrade!!.options.toList(), asg.categoricGrade!!)
|
val option = fdb.gradeCategoric ?: asg.categoricGrade!!.default
|
||||||
|
Categoric(option, asg.categoricGrade!!.options.toList(), asg.categoricGrade!!)
|
||||||
|
}
|
||||||
|
|
||||||
GradeType.NUMERIC -> Numeric(fdb.gradeNumeric!!, asg.numericGrade!!)
|
GradeType.NUMERIC -> {
|
||||||
|
val grade = fdb.gradeNumeric ?: -1.0
|
||||||
|
Numeric(grade, asg.numericGrade!!)
|
||||||
|
}
|
||||||
GradeType.PERCENTAGE -> Percentage(fdb.gradeNumeric!!)
|
GradeType.PERCENTAGE -> Percentage(fdb.gradeNumeric!!)
|
||||||
GradeType.NONE -> FreeText(fdb.gradeFreeText!!)
|
GradeType.NONE -> FreeText(fdb.gradeFreeText!!)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,12 +16,12 @@ sealed class UiGradeType {
|
|||||||
object FreeText : UiGradeType()
|
object FreeText : UiGradeType()
|
||||||
object Percentage : UiGradeType()
|
object Percentage : UiGradeType()
|
||||||
data class Numeric(val grade: NumericGrade) : UiGradeType()
|
data class Numeric(val grade: NumericGrade) : UiGradeType()
|
||||||
data class Categoric(val options: List<CategoricGradeOption>, val grade: CategoricGrade) : UiGradeType()
|
data class Categoric(val options: List<CategoricGradeOption>, val default: CategoricGradeOption?, val grade: CategoricGrade) : UiGradeType()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
context(trns: Transaction)
|
context(trns: Transaction)
|
||||||
fun from(type: GradeType, categoric: CategoricGrade?, numeric: NumericGrade?) = when(type) {
|
fun from(type: GradeType, categoric: CategoricGrade?, numeric: NumericGrade?) = when(type) {
|
||||||
GradeType.CATEGORIC -> Categoric(categoric!!.options.toList(), categoric)
|
GradeType.CATEGORIC -> Categoric(categoric!!.options.toList(), categoric.default, categoric)
|
||||||
GradeType.NUMERIC -> Numeric(numeric!!)
|
GradeType.NUMERIC -> Numeric(numeric!!)
|
||||||
GradeType.PERCENTAGE -> Percentage
|
GradeType.PERCENTAGE -> Percentage
|
||||||
GradeType.NONE -> FreeText
|
GradeType.NONE -> FreeText
|
||||||
|
|||||||
Reference in New Issue
Block a user