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) {
|
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||||
Text("No boards found.")
|
Text("No boards found.")
|
||||||
}
|
}
|
||||||
|
} else if (state.columns.isEmpty()) {
|
||||||
|
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||||
|
Text("No columns found for this board.")
|
||||||
|
}
|
||||||
} else {
|
} 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(
|
LazyRow(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
contentPadding = PaddingValues(16.dp),
|
contentPadding = PaddingValues(16.dp),
|
||||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
) {
|
) {
|
||||||
items(columns) { col ->
|
items(state.columns) { column ->
|
||||||
val colTasks = state.tasks.filter { it.status.lowercase() == col }
|
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(
|
Column(
|
||||||
modifier =
|
modifier =
|
||||||
@@ -170,7 +172,7 @@ fun KanbanScreen(
|
|||||||
.fillMaxSize(),
|
.fillMaxSize(),
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "${columnNames[col]} (${colTasks.size})",
|
text = "${colName.replaceFirstChar { it.uppercase() }} (${colTasks.size})",
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
modifier = Modifier.padding(bottom = 8.dp),
|
modifier = Modifier.padding(bottom = 8.dp),
|
||||||
@@ -181,30 +183,16 @@ fun KanbanScreen(
|
|||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
) {
|
) {
|
||||||
items(colTasks) { task ->
|
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(
|
TaskCard(
|
||||||
task = task,
|
task = task,
|
||||||
onMoveLeft = onMoveLeft,
|
onMoveLeft =
|
||||||
onMoveRight = onMoveRight,
|
prevColumn?.let {
|
||||||
|
{ viewModel.moveTask(task, it.name) }
|
||||||
|
},
|
||||||
|
onMoveRight =
|
||||||
|
nextColumn?.let {
|
||||||
|
{ viewModel.moveTask(task, it.name) }
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -219,7 +207,7 @@ fun KanbanScreen(
|
|||||||
AddTaskDialog(
|
AddTaskDialog(
|
||||||
onDismiss = { showAddTaskDialog = false },
|
onDismiss = { showAddTaskDialog = false },
|
||||||
onConfirm = { title, desc ->
|
onConfirm = { title, desc ->
|
||||||
viewModel.createTask(title, desc, "todo")
|
viewModel.createTask(title, desc, state.columns.firstOrNull()?.name ?: "todo")
|
||||||
showAddTaskDialog = false
|
showAddTaskDialog = false
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.m57.hermescontrol.ui.kanban
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.m57.hermescontrol.data.model.KanbanBoard
|
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.model.KanbanTask
|
||||||
import com.m57.hermescontrol.data.remote.ApiClient
|
import com.m57.hermescontrol.data.remote.ApiClient
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -17,6 +18,7 @@ data class KanbanUiState(
|
|||||||
val isLoading: Boolean = false,
|
val isLoading: Boolean = false,
|
||||||
val boards: List<KanbanBoard> = emptyList(),
|
val boards: List<KanbanBoard> = emptyList(),
|
||||||
val selectedBoard: KanbanBoard? = null,
|
val selectedBoard: KanbanBoard? = null,
|
||||||
|
val columns: List<KanbanColumn> = emptyList(),
|
||||||
val tasks: List<KanbanTask> = emptyList(),
|
val tasks: List<KanbanTask> = emptyList(),
|
||||||
val errorMessage: String? = null,
|
val errorMessage: String? = null,
|
||||||
val toastMessage: String? = null,
|
val toastMessage: String? = null,
|
||||||
@@ -75,13 +77,15 @@ class KanbanViewModel : ViewModel() {
|
|||||||
ApiClient.hermesApi.getKanbanBoard()
|
ApiClient.hermesApi.getKanbanBoard()
|
||||||
}
|
}
|
||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
val allTasks =
|
val body = response.body()
|
||||||
response
|
val allTasks = body?.columns?.flatMap { it.tasks }.orEmpty()
|
||||||
.body()
|
_uiState.update {
|
||||||
?.columns
|
it.copy(
|
||||||
?.flatMap { it.tasks }
|
isLoading = false,
|
||||||
.orEmpty()
|
columns = body?.columns.orEmpty(),
|
||||||
_uiState.update { it.copy(isLoading = false, tasks = allTasks) }
|
tasks = allTasks,
|
||||||
|
)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
|
|||||||
@@ -33,26 +33,21 @@ class SystemViewModel : ViewModel() {
|
|||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
ApiClient.hermesApi.getSystemStats()
|
ApiClient.hermesApi.getSystemStats()
|
||||||
}
|
}
|
||||||
val doctorResponse =
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
ApiClient.hermesApi.runDoctor()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (statsResponse.isSuccessful && doctorResponse.isSuccessful) {
|
if (statsResponse.isSuccessful) {
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
stats = statsResponse.body(),
|
stats = statsResponse.body(),
|
||||||
doctorReport = doctorResponse.body(),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
// Doctor is optional; many servers do not expose it.
|
||||||
|
loadDoctorReport()
|
||||||
} else {
|
} else {
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
errorMessage =
|
errorMessage = "Failed to load system stats: HTTP ${statsResponse.code()}",
|
||||||
"Failed to load system data: HTTP " +
|
|
||||||
"${statsResponse.code()} / ${doctorResponse.code()}",
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -60,13 +55,29 @@ class SystemViewModel : ViewModel() {
|
|||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
isLoading = false,
|
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() {
|
fun triggerBackup() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in new issue
Block a user