fix(system,kanban): make doctor optional and render dynamic kanban columns
This commit is contained in:
1 parent
a994c7d5db
commit
4c572509ec
3 files changed
+55
-52
No files matched your search
@@ -145,23 +145,25 @@ fun KanbanScreen(
|
||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
Text("No boards found.")
|
||||
}
|
||||
} else if (state.columns.isEmpty()) {
|
||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
Text("No columns found for this board.")
|
||||
}
|
||||
} else {
|
||||
// Columns list: To Do, In Progress, Done
|
||||
val columns = listOf("todo", "in_progress", "done")
|
||||
val columnNames =
|
||||
mapOf(
|
||||
"todo" to "To Do",
|
||||
"in_progress" to "In Progress",
|
||||
"done" to "Done",
|
||||
)
|
||||
|
||||
LazyRow(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentPadding = PaddingValues(16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
) {
|
||||
items(columns) { col ->
|
||||
val colTasks = state.tasks.filter { it.status.lowercase() == col }
|
||||
items(state.columns) { column ->
|
||||
val colName = column.name
|
||||
val colTasks =
|
||||
state.tasks.filter {
|
||||
it.status.equals(colName, ignoreCase = true)
|
||||
}
|
||||
val columnIndex = state.columns.indexOf(column)
|
||||
val prevColumn = state.columns.getOrNull(columnIndex - 1)
|
||||
val nextColumn = state.columns.getOrNull(columnIndex + 1)
|
||||
|
||||
Column(
|
||||
modifier =
|
||||
@@ -170,7 +172,7 @@ fun KanbanScreen(
|
||||
.fillMaxSize(),
|
||||
) {
|
||||
Text(
|
||||
text = "${columnNames[col]} (${colTasks.size})",
|
||||
text = "${colName.replaceFirstChar { it.uppercase() }} (${colTasks.size})",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier.padding(bottom = 8.dp),
|
||||
@@ -181,30 +183,16 @@ fun KanbanScreen(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
items(colTasks) { task ->
|
||||
val onMoveLeft: (() -> Unit)? =
|
||||
if (col != "todo") {
|
||||
{
|
||||
val prevStatus = if (col == "done") "in_progress" else "todo"
|
||||
viewModel.moveTask(task, prevStatus)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val onMoveRight: (() -> Unit)? =
|
||||
if (col != "done") {
|
||||
{
|
||||
val nextStatus = if (col == "todo") "in_progress" else "done"
|
||||
viewModel.moveTask(task, nextStatus)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
TaskCard(
|
||||
task = task,
|
||||
onMoveLeft = onMoveLeft,
|
||||
onMoveRight = onMoveRight,
|
||||
onMoveLeft =
|
||||
prevColumn?.let {
|
||||
{ viewModel.moveTask(task, it.name) }
|
||||
},
|
||||
onMoveRight =
|
||||
nextColumn?.let {
|
||||
{ viewModel.moveTask(task, it.name) }
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -219,7 +207,7 @@ fun KanbanScreen(
|
||||
AddTaskDialog(
|
||||
onDismiss = { showAddTaskDialog = false },
|
||||
onConfirm = { title, desc ->
|
||||
viewModel.createTask(title, desc, "todo")
|
||||
viewModel.createTask(title, desc, state.columns.firstOrNull()?.name ?: "todo")
|
||||
showAddTaskDialog = false
|
||||
},
|
||||
)
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.m57.hermescontrol.ui.kanban
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.m57.hermescontrol.data.model.KanbanBoard
|
||||
import com.m57.hermescontrol.data.model.KanbanColumn
|
||||
import com.m57.hermescontrol.data.model.KanbanTask
|
||||
import com.m57.hermescontrol.data.remote.ApiClient
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -17,6 +18,7 @@ data class KanbanUiState(
|
||||
val isLoading: Boolean = false,
|
||||
val boards: List<KanbanBoard> = emptyList(),
|
||||
val selectedBoard: KanbanBoard? = null,
|
||||
val columns: List<KanbanColumn> = emptyList(),
|
||||
val tasks: List<KanbanTask> = emptyList(),
|
||||
val errorMessage: String? = null,
|
||||
val toastMessage: String? = null,
|
||||
@@ -75,13 +77,15 @@ class KanbanViewModel : ViewModel() {
|
||||
ApiClient.hermesApi.getKanbanBoard()
|
||||
}
|
||||
if (response.isSuccessful) {
|
||||
val allTasks =
|
||||
response
|
||||
.body()
|
||||
?.columns
|
||||
?.flatMap { it.tasks }
|
||||
.orEmpty()
|
||||
_uiState.update { it.copy(isLoading = false, tasks = allTasks) }
|
||||
val body = response.body()
|
||||
val allTasks = body?.columns?.flatMap { it.tasks }.orEmpty()
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
isLoading = false,
|
||||
columns = body?.columns.orEmpty(),
|
||||
tasks = allTasks,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
|
||||
@@ -33,26 +33,21 @@ class SystemViewModel : ViewModel() {
|
||||
withContext(Dispatchers.IO) {
|
||||
ApiClient.hermesApi.getSystemStats()
|
||||
}
|
||||
val doctorResponse =
|
||||
withContext(Dispatchers.IO) {
|
||||
ApiClient.hermesApi.runDoctor()
|
||||
}
|
||||
|
||||
if (statsResponse.isSuccessful && doctorResponse.isSuccessful) {
|
||||
if (statsResponse.isSuccessful) {
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
isLoading = false,
|
||||
stats = statsResponse.body(),
|
||||
doctorReport = doctorResponse.body(),
|
||||
)
|
||||
}
|
||||
// Doctor is optional; many servers do not expose it.
|
||||
loadDoctorReport()
|
||||
} else {
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
isLoading = false,
|
||||
errorMessage =
|
||||
"Failed to load system data: HTTP " +
|
||||
"${statsResponse.code()} / ${doctorResponse.code()}",
|
||||
errorMessage = "Failed to load system stats: HTTP ${statsResponse.code()}",
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -60,13 +55,29 @@ class SystemViewModel : ViewModel() {
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
isLoading = false,
|
||||
errorMessage = "Failed to load system data: ${e.message}",
|
||||
errorMessage = "Failed to load system stats: ${e.message}",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadDoctorReport() {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val doctorResponse =
|
||||
withContext(Dispatchers.IO) {
|
||||
ApiClient.hermesApi.runDoctor()
|
||||
}
|
||||
if (doctorResponse.isSuccessful) {
|
||||
_uiState.update { it.copy(doctorReport = doctorResponse.body()) }
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
// Ignore optional doctor failures.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun triggerBackup() {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
|
||||
Reference in new issue
Block a user