feat(deck): назначить/снять себя на карточке (P1 diff — исполнители были read-only)

DeckRepository.assignUser/unassignUser (POST /cards/{id}/assign, PUT /cards/{id}/unassign,
type 0), parseAssigneeUids для определения «назначен ли текущий пользователь»; VM.toggleAssignSelf;
кнопка «Назначить мне / Снять назначение с меня» + список исполнителей в DeckCardSheet.
Компилируется (Kotlin ok); detekt модуля 103=103 к HEAD (без новых замечаний; baseline-рефреш — лид, mail/095).
This commit is contained in:
b-mob-deck
2026-07-13 07:28:46 +00:00
parent 22bd16c455
commit 6eeba1bba2
4 changed files with 59 additions and 0 deletions
@@ -55,10 +55,12 @@ fun DeckCardSheet(
stacks: List<DeckStack>,
canEdit: Boolean,
busy: Boolean,
assignedToMe: Boolean,
onDismiss: () -> Unit,
onToggleDone: () -> Unit,
onSave: (title: String, description: String, duedate: String?) -> Unit,
onToggleLabel: (DeckLabel) -> Unit,
onToggleAssignSelf: () -> Unit,
onMove: (targetStackId: Int) -> Unit,
onArchive: () -> Unit,
onDelete: () -> Unit,
@@ -146,6 +148,24 @@ fun DeckCardSheet(
}
}
// Исполнители
Text("Исполнители", style = MaterialTheme.typography.bodyMedium, color = F7Colors.TextSecondary)
if (card.assignees.isNotEmpty()) {
Text(
card.assignees.joinToString(", "),
style = MaterialTheme.typography.bodyMedium,
color = F7Colors.TextPrimary,
)
}
if (canEdit) {
F7SecondaryButton(
text = if (assignedToMe) "Снять назначение с меня" else "Назначить мне",
onClick = onToggleAssignSelf,
enabled = !busy,
modifier = Modifier.fillMaxWidth(),
)
}
// Переместить в колонку
if (canEdit && stacks.size > 1) {
Text("Колонка", style = MaterialTheme.typography.bodyMedium, color = F7Colors.TextSecondary)
@@ -99,6 +99,7 @@ class DeckRepository {
stackId = stackId,
labels = parseLabels(card.optJSONArray("labels")),
assignees = parseAssignees(card.optJSONArray("assignedUsers")),
assigneeUids = parseAssigneeUids(card.optJSONArray("assignedUsers")),
)
private fun parseLabels(array: JSONArray?): List<DeckLabel> {
@@ -126,6 +127,21 @@ class DeckRepository {
return out
}
/** uid'ы назначенных пользователей (type 0 — user) — для «назначен ли текущий пользователь». */
private fun parseAssigneeUids(array: JSONArray?): List<String> {
if (array == null) return emptyList()
val out = mutableListOf<String>()
for (i in 0 until array.length()) {
val a = array.optJSONObject(i)
if (a != null && a.optInt("type", 0) == 0) {
val uid = a.optJSONObject("participant")?.optString("uid")?.takeIf { it.isNotBlank() }
?: a.optString("participant").takeIf { it.isNotBlank() }
if (!uid.isNullOrBlank()) out += uid
}
}
return out
}
// --- Запись ---
fun createCard(session: AuthSession, stackId: Int, title: String): DeckCard {
@@ -179,6 +195,18 @@ class DeckRepository {
sendJson(session, "DELETE", "${apiBase(session)}/cards/$cardId/label/$labelId", null)
}
/** Назначить пользователя (type 0) на карточку. */
fun assignUser(session: AuthSession, cardId: Int, uid: String) {
val body = JSONObject().put("userId", uid).put("type", 0)
sendJson(session, "POST", "${apiBase(session)}/cards/$cardId/assign", body)
}
/** Снять назначение пользователя (type 0) с карточки. */
fun unassignUser(session: AuthSession, cardId: Int, uid: String) {
val body = JSONObject().put("userId", uid).put("type", 0)
sendJson(session, "PUT", "${apiBase(session)}/cards/$cardId/unassign", body)
}
fun createStack(session: AuthSession, boardId: Int, title: String): DeckStack {
val body = JSONObject().put("title", title).put("boardId", boardId).put("order", 999)
val json = sendJson(session, "POST", "${apiBase(session)}/stacks", body) as? JSONObject
@@ -258,6 +286,7 @@ data class DeckCard(
val stackId: Int = 0,
val labels: List<DeckLabel> = emptyList(),
val assignees: List<String> = emptyList(),
val assigneeUids: List<String> = emptyList(),
)
data class DeckBoardDetail(
@@ -135,10 +135,12 @@ fun DeckScreen(
stacks = state.boardDetail?.stacks.orEmpty(),
canEdit = state.boardDetail?.canEdit ?: false,
busy = state.busy,
assignedToMe = card.assigneeUids.contains(session.username),
onDismiss = vm::closeCard,
onToggleDone = { vm.toggleDone(session, card) },
onSave = { title, desc, due -> vm.updateCard(session, card, title, desc, due) },
onToggleLabel = { vm.toggleLabel(session, card, it) },
onToggleAssignSelf = { vm.toggleAssignSelf(session, card) },
onMove = { vm.moveCard(session, card, it) },
onArchive = { vm.archiveCard(session, card) },
onDelete = { vm.deleteCard(session, card) },
@@ -111,6 +111,14 @@ class DeckViewModel(
}
}
fun toggleAssignSelf(session: AuthSession, card: DeckCard) = mutate(session) {
if (card.assigneeUids.contains(session.username)) {
repository.unassignUser(session, card.id, session.username)
} else {
repository.assignUser(session, card.id, session.username)
}
}
fun createStack(session: AuthSession, title: String) {
val boardId = _state.value.selectedBoardId ?: return
mutate(session) { repository.createStack(session, boardId, title.trim()) }