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:
@@ -55,10 +55,12 @@ fun DeckCardSheet(
|
|||||||
stacks: List<DeckStack>,
|
stacks: List<DeckStack>,
|
||||||
canEdit: Boolean,
|
canEdit: Boolean,
|
||||||
busy: Boolean,
|
busy: Boolean,
|
||||||
|
assignedToMe: Boolean,
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
onToggleDone: () -> Unit,
|
onToggleDone: () -> Unit,
|
||||||
onSave: (title: String, description: String, duedate: String?) -> Unit,
|
onSave: (title: String, description: String, duedate: String?) -> Unit,
|
||||||
onToggleLabel: (DeckLabel) -> Unit,
|
onToggleLabel: (DeckLabel) -> Unit,
|
||||||
|
onToggleAssignSelf: () -> Unit,
|
||||||
onMove: (targetStackId: Int) -> Unit,
|
onMove: (targetStackId: Int) -> Unit,
|
||||||
onArchive: () -> Unit,
|
onArchive: () -> Unit,
|
||||||
onDelete: () -> 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) {
|
if (canEdit && stacks.size > 1) {
|
||||||
Text("Колонка", style = MaterialTheme.typography.bodyMedium, color = F7Colors.TextSecondary)
|
Text("Колонка", style = MaterialTheme.typography.bodyMedium, color = F7Colors.TextSecondary)
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ class DeckRepository {
|
|||||||
stackId = stackId,
|
stackId = stackId,
|
||||||
labels = parseLabels(card.optJSONArray("labels")),
|
labels = parseLabels(card.optJSONArray("labels")),
|
||||||
assignees = parseAssignees(card.optJSONArray("assignedUsers")),
|
assignees = parseAssignees(card.optJSONArray("assignedUsers")),
|
||||||
|
assigneeUids = parseAssigneeUids(card.optJSONArray("assignedUsers")),
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun parseLabels(array: JSONArray?): List<DeckLabel> {
|
private fun parseLabels(array: JSONArray?): List<DeckLabel> {
|
||||||
@@ -126,6 +127,21 @@ class DeckRepository {
|
|||||||
return out
|
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 {
|
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)
|
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 {
|
fun createStack(session: AuthSession, boardId: Int, title: String): DeckStack {
|
||||||
val body = JSONObject().put("title", title).put("boardId", boardId).put("order", 999)
|
val body = JSONObject().put("title", title).put("boardId", boardId).put("order", 999)
|
||||||
val json = sendJson(session, "POST", "${apiBase(session)}/stacks", body) as? JSONObject
|
val json = sendJson(session, "POST", "${apiBase(session)}/stacks", body) as? JSONObject
|
||||||
@@ -258,6 +286,7 @@ data class DeckCard(
|
|||||||
val stackId: Int = 0,
|
val stackId: Int = 0,
|
||||||
val labels: List<DeckLabel> = emptyList(),
|
val labels: List<DeckLabel> = emptyList(),
|
||||||
val assignees: List<String> = emptyList(),
|
val assignees: List<String> = emptyList(),
|
||||||
|
val assigneeUids: List<String> = emptyList(),
|
||||||
)
|
)
|
||||||
|
|
||||||
data class DeckBoardDetail(
|
data class DeckBoardDetail(
|
||||||
|
|||||||
@@ -135,10 +135,12 @@ fun DeckScreen(
|
|||||||
stacks = state.boardDetail?.stacks.orEmpty(),
|
stacks = state.boardDetail?.stacks.orEmpty(),
|
||||||
canEdit = state.boardDetail?.canEdit ?: false,
|
canEdit = state.boardDetail?.canEdit ?: false,
|
||||||
busy = state.busy,
|
busy = state.busy,
|
||||||
|
assignedToMe = card.assigneeUids.contains(session.username),
|
||||||
onDismiss = vm::closeCard,
|
onDismiss = vm::closeCard,
|
||||||
onToggleDone = { vm.toggleDone(session, card) },
|
onToggleDone = { vm.toggleDone(session, card) },
|
||||||
onSave = { title, desc, due -> vm.updateCard(session, card, title, desc, due) },
|
onSave = { title, desc, due -> vm.updateCard(session, card, title, desc, due) },
|
||||||
onToggleLabel = { vm.toggleLabel(session, card, it) },
|
onToggleLabel = { vm.toggleLabel(session, card, it) },
|
||||||
|
onToggleAssignSelf = { vm.toggleAssignSelf(session, card) },
|
||||||
onMove = { vm.moveCard(session, card, it) },
|
onMove = { vm.moveCard(session, card, it) },
|
||||||
onArchive = { vm.archiveCard(session, card) },
|
onArchive = { vm.archiveCard(session, card) },
|
||||||
onDelete = { vm.deleteCard(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) {
|
fun createStack(session: AuthSession, title: String) {
|
||||||
val boardId = _state.value.selectedBoardId ?: return
|
val boardId = _state.value.selectedBoardId ?: return
|
||||||
mutate(session) { repository.createStack(session, boardId, title.trim()) }
|
mutate(session) { repository.createStack(session, boardId, title.trim()) }
|
||||||
|
|||||||
Reference in New Issue
Block a user