fix(#1212): require bulk completion evidence

This commit is contained in:
M57 committed 2026-09-22 08:37:49 +03:00
1 parent 9f16ba2929
commit 98c417ae84
4 files changed
+109 -7

No files matched your search

@@ -151,6 +151,7 @@ fun KanbanScreen(
var isMultiSelectMode by remember { mutableStateOf(false) }
var selectedTaskIds by remember { mutableStateOf(setOf<String>()) }
var showBulkMoveDialog by remember { mutableStateOf(false) }
var showBulkCompleteDialog by remember { mutableStateOf(false) }
var showBulkAssignDialog by remember { mutableStateOf(false) }
var showBulkDeleteDialog by remember { mutableStateOf(false) }
@@ -1052,9 +1053,13 @@ fun KanbanScreen(
writableMoveColumns.forEach { col ->
TextButton(
onClick = {
viewModel.bulkMove(selectedTaskIds.toList(), col.name) { failed ->
selectedTaskIds = failed
if (failed.isEmpty()) isMultiSelectMode = false
if (col.name.equals("done", ignoreCase = true)) {
showBulkCompleteDialog = true
} else {
viewModel.bulkMove(selectedTaskIds.toList(), col.name) { failed ->
selectedTaskIds = failed
if (failed.isEmpty()) isMultiSelectMode = false
}
}
showBulkMoveDialog = false
},
@@ -1074,6 +1079,24 @@ fun KanbanScreen(
)
}
if (showBulkCompleteDialog) {
BulkCompleteTasksDialog(
taskCount = selectedTaskIds.size,
onConfirm = { summary ->
viewModel.bulkMove(
taskIds = selectedTaskIds.toList(),
targetStatus = "done",
summary = summary,
) { failed ->
selectedTaskIds = failed
if (failed.isEmpty()) isMultiSelectMode = false
}
showBulkCompleteDialog = false
},
onDismiss = { showBulkCompleteDialog = false },
)
}
if (showBulkAssignDialog) {
AlertDialog(
onDismissRequest = { showBulkAssignDialog = false },
@@ -1431,6 +1454,43 @@ private fun CompleteTaskDialog(
)
}
@Composable
private fun BulkCompleteTasksDialog(
taskCount: Int,
onConfirm: (String) -> Unit,
onDismiss: () -> Unit,
) {
var summary by remember { mutableStateOf("") }
val trimmedSummary = summary.trim()
AlertDialog(
onDismissRequest = onDismiss,
title = { Text(stringResource(R.string.kanban_bulk_complete_title, taskCount)) },
text = {
OutlinedTextField(
value = summary,
onValueChange = { summary = it },
label = { Text(stringResource(R.string.kanban_complete_summary_label)) },
placeholder = { Text(stringResource(R.string.kanban_complete_summary_hint)) },
modifier = Modifier.fillMaxWidth(),
minLines = 2,
)
},
confirmButton = {
Button(
onClick = { onConfirm(trimmedSummary) },
enabled = trimmedSummary.isNotEmpty(),
) {
Text(stringResource(R.string.kanban_action_complete))
}
},
dismissButton = {
TextButton(onClick = onDismiss) {
Text(stringResource(R.string.action_cancel))
}
},
)
}
private fun KanbanTaskAction.labelRes(): Int =
when (this) {
KanbanTaskAction.TRIAGE -> R.string.kanban_action_triage
@@ -618,12 +618,26 @@ class KanbanViewModel(
fun bulkMove(
taskIds: List<String>,
targetStatus: String,
summary: String? = null,
onComplete: ((failedIds: Set<String>) -> Unit)? = null,
) {
val board = _uiState.value.selectedBoard ?: return
val completionSummary = summary?.trim()?.takeIf { it.isNotEmpty() }
if (targetStatus.equals("done", ignoreCase = true) && completionSummary == null) {
_uiState.update { it.copy(toastMessage = "Completion summary is required") }
onComplete?.invoke(taskIds.toSet())
return
}
viewModelScope.launch {
_uiState.update { it.copy(isLoading = true) }
when (val res = repository.bulkTasks(board.id, BulkTasksBody(ids = taskIds, status = targetStatus))) {
val body =
BulkTasksBody(
ids = taskIds,
status = targetStatus,
summary = completionSummary,
result = completionSummary,
)
when (val res = repository.bulkTasks(board.id, body)) {
is NetworkResult.Success -> {
val results = res.data.results
val succeeded = results.filter { it.ok }.map { it.id }.toSet()