diff --git a/composeApp/src/desktopMain/kotlin/com/jaytux/grader/App.kt b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/App.kt index 94b3725..ac8098a 100644 --- a/composeApp/src/desktopMain/kotlin/com/jaytux/grader/App.kt +++ b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/App.kt @@ -1,23 +1,43 @@ package com.jaytux.grader -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface +import androidx.compose.foundation.layout.* +import androidx.compose.material3.* import androidx.compose.runtime.* +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import com.jaytux.grader.ui.ChevronLeft import com.jaytux.grader.ui.CoursesView +import com.jaytux.grader.ui.toDp import com.jaytux.grader.viewmodel.CourseListState import org.jetbrains.compose.ui.tooling.preview.Preview +data class UiRoute(val heading: String, val content: @Composable (push: (UiRoute) -> Unit) -> Unit) + @Composable @Preview fun App() { MaterialTheme { val courseList = CourseListState() - Surface(Modifier.fillMaxSize().padding(10.dp)) { - CoursesView(courseList) + var stack by remember { + val start = UiRoute("Courses Overview") { CoursesView(courseList, it) } + mutableStateOf(listOf(start)) + } + + Column { + Surface(Modifier.fillMaxWidth(), color = MaterialTheme.colorScheme.primary, tonalElevation = 10.dp, shadowElevation = 10.dp) { + Row(Modifier.padding(10.dp)) { + IconButton({ stack = stack.toMutableList().also { it.removeLast() } }, enabled = stack.size >= 2) { + Icon(ChevronLeft, "Back", Modifier.size(MaterialTheme.typography.headlineLarge.fontSize.toDp())) + } + Text(stack.last().heading, Modifier.align(Alignment.CenterVertically), style = MaterialTheme.typography.headlineLarge) + } + } + Surface(Modifier.fillMaxSize()) { + Box(Modifier.padding(10.dp)) { + stack.last().content { stack += (it) } + } + } } } } \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/com/jaytux/grader/data/DSL.kt b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/data/DSL.kt index ac4e2fe..a04d557 100644 --- a/composeApp/src/desktopMain/kotlin/com/jaytux/grader/data/DSL.kt +++ b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/data/DSL.kt @@ -28,17 +28,19 @@ object Groups : UUIDTable("groups") { object Students : UUIDTable("students") { val name = varchar("name", 50) + val contact = varchar("contact", 50) val note = text("note") } -object GroupStudents : Table("grpStudents") { +object GroupStudents : CompositeIdTable("grpStudents") { val groupId = reference("group_id", Groups.id) val studentId = reference("student_id", Students.id) + val role = varchar("role", 50).nullable() override val primaryKey = PrimaryKey(groupId, studentId) } -object SoloStudents : Table("soloStudents") { +object EditionStudents : Table("editionStudents") { val editionId = reference("edition_id", Editions.id) val studentId = reference("student_id", Students.id) @@ -46,15 +48,15 @@ object SoloStudents : Table("soloStudents") { } object GroupAssignments : UUIDTable("grpAssgmts") { - val editionId = reference("edition_id_", Editions.id) - val name = varchar("name_", 50) - val assignment = text("assignment_") + val editionId = reference("edition_id", Editions.id) + val name = varchar("name", 50) + val assignment = text("assignment") } object SoloAssignments : UUIDTable("soloAssgmts") { - val editionId = GroupAssignments.reference("edition_id", Editions.id) - val name = GroupAssignments.varchar("name", 50) - val assignment = GroupAssignments.text("assignment") + val editionId = reference("edition_id", Editions.id) + val name = varchar("name", 50) + val assignment = text("assignment") } object GroupFeedbacks : CompositeIdTable("grpFdbks") { diff --git a/composeApp/src/desktopMain/kotlin/com/jaytux/grader/data/Database.kt b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/data/Database.kt index 2e825bd..3f0a272 100644 --- a/composeApp/src/desktopMain/kotlin/com/jaytux/grader/data/Database.kt +++ b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/data/Database.kt @@ -10,7 +10,7 @@ object Database { transaction { SchemaUtils.create( Courses, Editions, Groups, - Students, GroupStudents, SoloStudents, + Students, GroupStudents, EditionStudents, GroupAssignments, SoloAssignments, GroupFeedbacks, IndividualFeedbacks, SoloFeedbacks ) diff --git a/composeApp/src/desktopMain/kotlin/com/jaytux/grader/data/Entities.kt b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/data/Entities.kt index 32cee68..4f343b2 100644 --- a/composeApp/src/desktopMain/kotlin/com/jaytux/grader/data/Entities.kt +++ b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/data/Entities.kt @@ -21,7 +21,9 @@ class Edition(id: EntityID) : Entity(id) { var course by Course referencedOn Editions.courseId var name by Editions.name val groups by Group referrersOn Groups.editionId - val soloStudents by Student via SoloStudents + val soloStudents by Student via EditionStudents + val soloAssignments by SoloAssignment referrersOn SoloAssignments.editionId +// val groupAssignments by GroupAssignment referrersOn GroupAssignments.editionId } class Group(id: EntityID) : Entity(id) { @@ -37,8 +39,9 @@ class Student(id: EntityID) : Entity(id) { var name by Students.name var note by Students.note + var contact by Students.contact val groups by Group via GroupStudents - val soloCourses by Edition via SoloStudents + val courses by Edition via EditionStudents } class GroupAssignment(id: EntityID) : Entity(id) { diff --git a/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/Courses.kt b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/Courses.kt index 98f849b..24f9da3 100644 --- a/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/Courses.kt +++ b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/Courses.kt @@ -1,5 +1,6 @@ package com.jaytux.grader.ui +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items @@ -9,7 +10,6 @@ import androidx.compose.material.OutlinedTextField import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Edit -import androidx.compose.material.icons.filled.PlayArrow import androidx.compose.material3.Button import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface @@ -20,12 +20,17 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.* -import com.jaytux.grader.data.Course +import androidx.compose.ui.window.DialogWindow +import androidx.compose.ui.window.WindowPosition +import androidx.compose.ui.window.rememberDialogState +import com.jaytux.grader.UiRoute +import com.jaytux.grader.data.Edition import com.jaytux.grader.viewmodel.CourseListState +import com.jaytux.grader.viewmodel.EditionListState +import com.jaytux.grader.viewmodel.EditionState @Composable -fun CoursesView(state: CourseListState) { +fun CoursesView(state: CourseListState, push: (UiRoute) -> Unit) { val data by state.courses.entities var showDialog by remember { mutableStateOf(false) } @@ -41,7 +46,7 @@ fun CoursesView(state: CourseListState) { } else { LazyColumn(Modifier.fillMaxSize()) { - items(data) { CourseWidget(it) { state.delete(it) } } + items(data) { CourseWidget(state.getEditions(it), { state.delete(it) }, push) } item { Button({ showDialog = true }, Modifier.fillMaxWidth()) { @@ -51,45 +56,71 @@ fun CoursesView(state: CourseListState) { } } - if(showDialog) AddCourseDialog(data.map { it.name }, { showDialog = false }) { state.new(it) } + if(showDialog) AddStringDialog("Course name", data.map { it.name }, { showDialog = false }) { state.new(it) } } @Composable -fun AddCourseDialog(taken: List, onClose: () -> Unit, onSave: (String) -> Unit) = DialogWindow( - onCloseRequest = onClose, - state = rememberDialogState(size = DpSize(400.dp, 300.dp), position = WindowPosition(Alignment.Center)) -) { - Surface(Modifier.fillMaxSize().padding(10.dp)) { - Box(Modifier.fillMaxSize()) { - var name by remember { mutableStateOf("") } - Column(Modifier.align(Alignment.Center)) { - OutlinedTextField(name, { name = it }, Modifier.fillMaxWidth(), label = { Text("Course name") }, isError = name in taken) - Row { - Button({ onClose() }, Modifier.weight(0.45f)) { Text("Cancel") } - Spacer(Modifier.weight(0.1f)) - Button({ onSave(name); onClose() }, Modifier.weight(0.45f)) { Text("Save") } - } - } +fun CourseWidget(state: EditionListState, onDelete: () -> Unit, push: (UiRoute) -> Unit) { + val editions by state.editions.entities + var isOpened by remember { mutableStateOf(false) } + var showDialog by remember { mutableStateOf(false) } + + val callback = { it: Edition -> + val s = EditionState(it) + val route = UiRoute("${state.course.name}: ${it.name}") { + EditionView(s) } + push(route) } -} -@Composable -fun CourseWidget(course: Course, onDelete: () -> Unit) { - val editions = remember(course) { course.loadEditions().size } - Surface(Modifier.fillMaxWidth().padding(horizontal = 5.dp, vertical = 10.dp), shape = MaterialTheme.shapes.medium, tonalElevation = 2.dp, shadowElevation = 2.dp) { + Surface(Modifier.fillMaxWidth().padding(horizontal = 5.dp, vertical = 10.dp).clickable { isOpened = !isOpened }, shape = MaterialTheme.shapes.medium, tonalElevation = 2.dp, shadowElevation = 2.dp) { Row { - Column(Modifier.weight(1f)) { - Text(course.name, Modifier.padding(5.dp), style = MaterialTheme.typography.headlineMedium) + Column(Modifier.weight(1f).padding(5.dp)) { Row { - Spacer(Modifier.width(15.dp)) - Text("$editions editions", fontStyle = FontStyle.Italic) + Icon( + if (isOpened) ChevronDown else ChevronRight, "Toggle editions", + Modifier.size(MaterialTheme.typography.headlineMedium.fontSize.toDp()) + .align(Alignment.CenterVertically) + ) + Column { + Text(state.course.name, style = MaterialTheme.typography.headlineMedium) + } + } + Row { + Spacer(Modifier.width(25.dp)) + Text( + "${editions.size} edition(s)", + fontStyle = FontStyle.Italic, + style = MaterialTheme.typography.bodySmall + ) + } + + if(isOpened) { + Row { + Spacer(Modifier.width(25.dp)) + Column { + editions.forEach { EditionWidget(it, { callback(it) }) { state.delete(it) } } + Button({ showDialog = true }, Modifier.fillMaxWidth()) { Text("Add edition") } + } + } } } Column { IconButton({ onDelete() }) { Icon(Icons.Default.Delete, "Remove") } - IconButton({ TODO() }) { Icon(Icons.Default.Edit, "Edit") } + IconButton({ TODO() }, enabled = false) { Icon(Icons.Default.Edit, "Edit") } } } } + + if(showDialog) AddStringDialog("Edition name", editions.map { it.name }, { showDialog = false }) { state.new(it) } +} + +@Composable +fun EditionWidget(edition: Edition, onOpen: () -> Unit, onDelete: () -> Unit) { + Surface(Modifier.fillMaxWidth().padding(horizontal = 5.dp, vertical = 10.dp).clickable { onOpen() }, shape = MaterialTheme.shapes.medium, tonalElevation = 2.dp, shadowElevation = 2.dp) { + Row(Modifier.padding(5.dp)) { + Text(edition.name, Modifier.weight(1f), style = MaterialTheme.typography.headlineSmall) + IconButton({ onDelete() }) { Icon(Icons.Default.Delete, "Remove") } + } + } } \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/Editions.kt b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/Editions.kt new file mode 100644 index 0000000..a4f02aa --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/Editions.kt @@ -0,0 +1,186 @@ +package com.jaytux.grader.ui + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.material.Checkbox +import androidx.compose.material.OutlinedTextField +import androidx.compose.material3.Button +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.runtime.* +import androidx.compose.material3.Text +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.DialogWindow +import androidx.compose.ui.window.WindowPosition +import androidx.compose.ui.window.rememberDialogState +import com.jaytux.grader.data.* +import com.jaytux.grader.viewmodel.EditionState + +@Composable +fun EditionView(state: EditionState) = Row { + var isGroup by remember { mutableStateOf(false) } + + val students by state.students.entities + val groups by state.groups.entities + val solo by state.solo.entities + val groupAs by state.groupAs//.entities + + TabLayout( + listOf("Students", "Groups"), + if(isGroup) 1 else 0, + { isGroup = it == 1 }, + { Text(it) }, + Modifier.weight(0.25f) + ) { + Column(Modifier.fillMaxSize()) { + if(isGroup) { + Box(Modifier.weight(0.5f)) { + GroupsWidget(state.course, state.edition, groups, {}) { state.newGroup(it) } + } + Box(Modifier.weight(0.5f)) { GroupAssignmentsWidget(groupAs, {}) {} } + } + else { + Box(Modifier.weight(0.5f)) { + StudentsWidget(state.course, state.edition, students, {}) { name, note, contact, addToEdition -> + state.newStudent(name, note, contact, addToEdition) + } + } + Box(Modifier.weight(0.5f)) { AssignmentsWidget(solo, {}) {} } + } + } + } + Box(Modifier.weight(0.75f)) {} +} + +@Composable +fun StudentsWidget( + course: Course, + edition: Edition, + students: List, + onSelect: (Int) -> Unit, + onAdd: (name: String, note: String, contact: String, addToEdition: Boolean) -> Unit +) = Column(Modifier.padding(10.dp)) { + Text("Student list", style = MaterialTheme.typography.headlineMedium) + var showDialog by remember { mutableStateOf(false) } + if(students.isEmpty()) { + Box(Modifier.fillMaxSize()) { + Column(Modifier.align(Alignment.Center)) { + Text( + "Course ${course.name} (edition ${edition.name})\nhas no students yet.", + Modifier.align(Alignment.CenterHorizontally), + textAlign = TextAlign.Center + ) + Button({ showDialog = true }, Modifier.align(Alignment.CenterHorizontally)) { + Text("Add a student") + } + } + } + } + else { + LazyColumn(Modifier.padding(5.dp).weight(1f)) { + itemsIndexed(students) { idx, it -> + Surface(Modifier.fillMaxWidth().clickable { onSelect(idx) }) { + Text(it.name, Modifier.padding(5.dp)) + } + } + } + + Button({ showDialog = true }, Modifier.fillMaxWidth()) { + Text("Add a student") + } + } + + if(showDialog) StudentDialog(course, edition, { showDialog = false }, onAdd) +} + +@Composable +fun StudentDialog( + course: Course, + edition: Edition, + onClose: () -> Unit, + onAdd: (name: String, note: String, contact: String, addToEdition: Boolean) -> Unit +) = DialogWindow( + onCloseRequest = onClose, + state = rememberDialogState(size = DpSize(600.dp, 400.dp), position = WindowPosition(Alignment.Center)) +) { + Surface(Modifier.fillMaxSize().padding(10.dp)) { + Box(Modifier.fillMaxSize()) { + var name by remember { mutableStateOf("") } + var contact by remember { mutableStateOf("") } + var note by remember { mutableStateOf("") } + var add by remember { mutableStateOf(true) } + + 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() + } + } + } + } +} + +@Composable +fun GroupsWidget( + course: Course, + edition: Edition, + groups: List, + onSelect: (Int) -> Unit, + onAdd: (name: String) -> Unit +) = Column(Modifier.padding(10.dp)) { + Text("Group list", style = MaterialTheme.typography.headlineMedium) + var showDialog by remember { mutableStateOf(false) } + + if(groups.isEmpty()) { + Box(Modifier.fillMaxSize()) { + Column(Modifier.align(Alignment.Center)) { + Text( + "Course ${course.name} (edition ${edition.name})\nhas no groups yet.", + Modifier.align(Alignment.CenterHorizontally), + textAlign = TextAlign.Center + ) + Button({ showDialog = true }, Modifier.align(Alignment.CenterHorizontally)) { + Text("Add a group") + } + } + } + } + else { + LazyColumn(Modifier.padding(5.dp).weight(1f)) { + itemsIndexed(groups) { idx, it -> + Surface(Modifier.fillMaxWidth().clickable { onSelect(idx) }) { + Text(it.name, Modifier.padding(5.dp)) + } + } + } + + Button({ showDialog = true }, Modifier.fillMaxWidth()) { + Text("Add a group") + } + } + + if(showDialog) AddStringDialog("Group name", groups.map { it.name }, { showDialog = false }) { onAdd(it) } +} + +@Composable +fun AssignmentsWidget(assignments: List, onSelect: (Int) -> Unit, onAdd: (name: String) -> Unit) { + // +} + +@Composable +fun GroupAssignmentsWidget(assignments: List, onSelect: (Int) -> Unit, onAdd: (name: String) -> Unit) { + // +} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/Icons.kt b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/Icons.kt new file mode 100644 index 0000000..fe5507f --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/Icons.kt @@ -0,0 +1,88 @@ +package com.jaytux.grader.ui + +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathFillType +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.StrokeJoin +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.unit.dp + +val ChevronRight: ImageVector by lazy { + ImageVector.Builder( + name = "ChevronRight", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path( + fill = null, + fillAlpha = 1.0f, + stroke = SolidColor(Color(0xFF000000)), + strokeAlpha = 1.0f, + strokeLineWidth = 2f, + strokeLineCap = StrokeCap.Round, + strokeLineJoin = StrokeJoin.Round, + strokeLineMiter = 1.0f, + pathFillType = PathFillType.NonZero + ) { + moveTo(9f, 18f) + lineToRelative(6f, -6f) + lineToRelative(-6f, -6f) + } + }.build() +} + +val ChevronDown: ImageVector by lazy { + ImageVector.Builder( + name = "ChevronDown", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path( + fill = null, + fillAlpha = 1.0f, + stroke = SolidColor(Color(0xFF000000)), + strokeAlpha = 1.0f, + strokeLineWidth = 2f, + strokeLineCap = StrokeCap.Round, + strokeLineJoin = StrokeJoin.Round, + strokeLineMiter = 1.0f, + pathFillType = PathFillType.NonZero + ) { + moveTo(6f, 9f) + lineToRelative(6f, 6f) + lineToRelative(6f, -6f) + } + }.build() +} + +public val ChevronLeft: ImageVector by lazy { + ImageVector.Builder( + name = "ChevronLeft", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path( + fill = null, + fillAlpha = 1.0f, + stroke = SolidColor(Color(0xFF000000)), + strokeAlpha = 1.0f, + strokeLineWidth = 2f, + strokeLineCap = StrokeCap.Round, + strokeLineJoin = StrokeJoin.Round, + strokeLineMiter = 1.0f, + pathFillType = PathFillType.NonZero + ) { + moveTo(15f, 18f) + lineToRelative(-6f, -6f) + lineToRelative(6f, -6f) + } + }.build() +} diff --git a/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/Util.kt b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/Util.kt new file mode 100644 index 0000000..208ae64 --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/Util.kt @@ -0,0 +1,9 @@ +package com.jaytux.grader.ui + +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.TextUnit + +@Composable +fun TextUnit.toDp(): Dp = with(LocalDensity.current) { value.toDp() } \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/Widgets.kt b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/Widgets.kt new file mode 100644 index 0000000..788521a --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/Widgets.kt @@ -0,0 +1,67 @@ +package com.jaytux.grader.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.CornerSize +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.DialogWindow +import androidx.compose.ui.window.WindowPosition +import androidx.compose.ui.window.rememberDialogState + +@Composable +fun CancelSaveRow(canSave: Boolean, onCancel: () -> Unit, onSave: () -> Unit) { + Row { + Button({ onCancel() }, Modifier.weight(0.45f)) { Text("Cancel") } + Spacer(Modifier.weight(0.1f)) + Button({ onSave() }, Modifier.weight(0.45f), enabled = canSave) { Text("Save") } + } +} + +@Composable +fun TabLayout( + options: List, + currentIndex: Int, + onSelect: (Int) -> Unit, + optionContent: @Composable (T) -> Unit, + modifier: Modifier = Modifier, + content: @Composable () -> Unit +) = Column(modifier) { + TabRow(currentIndex) { + options.forEachIndexed { idx, it -> + Tab( + selected = idx == currentIndex, + onClick = { onSelect(idx) }, + text = { optionContent(it) } + ) + } + } + content() +} + +@Composable +fun AddStringDialog(label: String, taken: List, onClose: () -> Unit, onSave: (String) -> Unit) = DialogWindow( + onCloseRequest = onClose, + state = rememberDialogState(size = DpSize(400.dp, 300.dp), position = WindowPosition(Alignment.Center)) +) { + Surface(Modifier.fillMaxSize().padding(10.dp)) { + Box(Modifier.fillMaxSize()) { + var name by remember { mutableStateOf("") } + Column(Modifier.align(Alignment.Center)) { + androidx.compose.material.OutlinedTextField(name, { name = it }, Modifier.fillMaxWidth(), label = { Text(label) }, isError = name in taken) + CancelSaveRow(name.isNotBlank() && name !in taken, onClose) { + onSave(name) + onClose() + } + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/com/jaytux/grader/viewmodel/DbState.kt b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/viewmodel/DbState.kt index f2eac2d..5170800 100644 --- a/composeApp/src/desktopMain/kotlin/com/jaytux/grader/viewmodel/DbState.kt +++ b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/viewmodel/DbState.kt @@ -3,11 +3,10 @@ package com.jaytux.grader.viewmodel import androidx.compose.runtime.MutableState import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf -import com.jaytux.grader.data.Course -import com.jaytux.grader.data.Edition -import com.jaytux.grader.data.Editions +import com.jaytux.grader.data.* import org.jetbrains.exposed.dao.Entity import org.jetbrains.exposed.sql.Transaction +import org.jetbrains.exposed.sql.insert import org.jetbrains.exposed.sql.transactions.transaction import java.util.* @@ -36,8 +35,47 @@ class CourseListState { transaction { course.delete() } courses.refresh() } + + fun getEditions(course: Course) = EditionListState(course) } -class EditionListState(private val course: Course) { +class EditionListState(val course: Course) { val editions = RawDbState { Edition.find { Editions.courseId eq course.id }.toList() } + + fun new(name: String) { + transaction { Edition.new { this.name = name; this.course = this@EditionListState.course } } + editions.refresh() + } + + fun delete(edition: Edition) { + transaction { edition.delete() } + editions.refresh() + } +} + +class EditionState(val edition: Edition) { + val course = transaction { edition.course } + val students = RawDbState { edition.soloStudents.toList() } + val groups = RawDbState { edition.groups.toList() } + val groupAs = mutableStateOf(listOf()) + val solo = RawDbState { edition.soloAssignments.toList() } + + fun newStudent(name: String, contact: String, note: String, addToEdition: Boolean) { + transaction { + val student = Student.new { this.name = name; this.contact = contact; this.note = note } + if(addToEdition) EditionStudents.insert { + it[editionId] = edition.id + it[studentId] = student.id + } + } + + if(addToEdition) students.refresh() + } + + fun newGroup(name: String) { + transaction { + Group.new { this.name = name; this.edition = this@EditionState.edition } + groups.refresh() + } + } } \ No newline at end of file