fix: improve kanban board filtering (#919)
- Render only the selected status column when status chips are active - Add status color treatments and default assignee normalization - Reuse profile avatars for Kanban card assignee tags - Cover status filtering, default assignee labels, and avatar rendering
This commit is contained in:
@@ -3,6 +3,7 @@ import { ref, computed } from 'vue'
|
||||
import { NModal, NForm, NFormItem, NInput, NSelect, NButton, useMessage } from 'naive-ui'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useKanbanStore } from '@/stores/hermes/kanban'
|
||||
import { withDefaultAssignee } from '@/utils/hermes/kanban-assignees'
|
||||
|
||||
const emit = defineEmits<{
|
||||
close: []
|
||||
@@ -26,10 +27,8 @@ const priorityOptions = computed(() => [
|
||||
])
|
||||
|
||||
const assigneeOptions = computed(() => {
|
||||
return kanbanStore.assignees.map(a => {
|
||||
const total = Object.values(a.counts || {}).reduce((s, c) => s + c, 0)
|
||||
return { label: `${a.name} · ${t('kanban.stats.tasks')}: ${total}`, value: a.name }
|
||||
})
|
||||
return withDefaultAssignee(kanbanStore.assignees, kanbanStore.stats?.by_assignee || {})
|
||||
.map(a => ({ label: a.name, value: a.name }))
|
||||
})
|
||||
|
||||
async function handleSubmit() {
|
||||
|
||||
@@ -2,10 +2,13 @@
|
||||
import { computed } from 'vue'
|
||||
import { NTooltip } from 'naive-ui'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import ProfileAvatar from '@/components/hermes/profiles/ProfileAvatar.vue'
|
||||
import type { KanbanTask } from '@/api/hermes/kanban'
|
||||
import type { ProfileAvatar as ProfileAvatarData } from '@/api/hermes/profiles'
|
||||
|
||||
const props = defineProps<{
|
||||
task: KanbanTask
|
||||
assigneeAvatar?: ProfileAvatarData | null
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -34,12 +37,21 @@ const priorityText = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="kanban-task-card" @click="emit('click', task.id)">
|
||||
<div class="kanban-task-card" :class="`status-${task.status}`" @click="emit('click', task.id)">
|
||||
<div class="card-title">{{ task.title }}</div>
|
||||
<div class="card-meta">
|
||||
<NTooltip v-if="task.assignee" trigger="hover">
|
||||
<template #trigger>
|
||||
<span class="meta-tag assignee-tag">{{ task.assignee }}</span>
|
||||
<span class="meta-tag assignee-tag">
|
||||
<ProfileAvatar
|
||||
class="assignee-profile-avatar"
|
||||
:name="task.assignee"
|
||||
:avatar="assigneeAvatar"
|
||||
:size="18"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span>{{ task.assignee }}</span>
|
||||
</span>
|
||||
</template>
|
||||
{{ t('kanban.card.assigneeTooltip') }}
|
||||
</NTooltip>
|
||||
@@ -54,15 +66,25 @@ const priorityText = computed(() => {
|
||||
@use '@/styles/variables' as *;
|
||||
|
||||
.kanban-task-card {
|
||||
--kanban-card-status-color: #64748b;
|
||||
background-color: $bg-card;
|
||||
border: 1px solid $border-color;
|
||||
border-left: 3px solid var(--kanban-card-status-color);
|
||||
border-radius: $radius-md;
|
||||
padding: 12px;
|
||||
cursor: pointer;
|
||||
transition: border-color $transition-fast, box-shadow $transition-fast;
|
||||
|
||||
&.status-triage { --kanban-card-status-color: #94a3b8; }
|
||||
&.status-todo { --kanban-card-status-color: #38bdf8; }
|
||||
&.status-ready { --kanban-card-status-color: #f59e0b; }
|
||||
&.status-running { --kanban-card-status-color: #8b5cf6; }
|
||||
&.status-blocked { --kanban-card-status-color: #ef4444; }
|
||||
&.status-done { --kanban-card-status-color: #22c55e; }
|
||||
&.status-archived { --kanban-card-status-color: #64748b; }
|
||||
|
||||
&:hover {
|
||||
border-color: rgba(var(--accent-primary-rgb), 0.3);
|
||||
border-color: var(--kanban-card-status-color);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
}
|
||||
@@ -91,8 +113,16 @@ const priorityText = computed(() => {
|
||||
}
|
||||
|
||||
.assignee-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
background: rgba(var(--accent-primary-rgb), 0.1);
|
||||
color: $accent-primary;
|
||||
padding-left: 2px;
|
||||
}
|
||||
|
||||
.assignee-profile-avatar {
|
||||
box-shadow: 0 0 0 1px rgba(var(--accent-primary-rgb), 0.28);
|
||||
}
|
||||
|
||||
.priority-tag {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useRouter } from 'vue-router'
|
||||
import { request } from '@/api/client'
|
||||
import { getTask } from '@/api/hermes/kanban'
|
||||
import { useKanbanStore } from '@/stores/hermes/kanban'
|
||||
import { withDefaultAssignee } from '@/utils/hermes/kanban-assignees'
|
||||
import HistoryMessageList from '@/components/hermes/chat/HistoryMessageList.vue'
|
||||
import type { Session, Message } from '@/stores/hermes/chat'
|
||||
import type { KanbanTaskDetail } from '@/api/hermes/kanban'
|
||||
@@ -106,10 +107,8 @@ const historySession = computed<Session | null>(() => {
|
||||
})
|
||||
|
||||
const assigneeOptions = computed(() => {
|
||||
return kanbanStore.assignees.map(a => {
|
||||
const total = Object.values(a.counts || {}).reduce((s, c) => s + c, 0)
|
||||
return { label: `${a.name} · ${t('kanban.stats.tasks')}: ${total}`, value: a.name }
|
||||
})
|
||||
return withDefaultAssignee(kanbanStore.assignees, kanbanStore.stats?.by_assignee || {})
|
||||
.map(a => ({ label: a.name, value: a.name }))
|
||||
})
|
||||
|
||||
watch(() => [props.taskId, kanbanStore.selectedBoard] as const, async ([id, board]) => {
|
||||
@@ -482,19 +481,14 @@ async function handleAssign() {
|
||||
border-radius: 4px;
|
||||
font-weight: 500;
|
||||
|
||||
&.running {
|
||||
background: rgba(var(--accent-primary-rgb), 0.12);
|
||||
color: $accent-primary;
|
||||
&.triage {
|
||||
background: rgba(148, 163, 184, 0.14);
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
&.done {
|
||||
background: rgba(var(--success-rgb), 0.12);
|
||||
color: $success;
|
||||
}
|
||||
|
||||
&.blocked {
|
||||
background: rgba(var(--error-rgb), 0.12);
|
||||
color: $error;
|
||||
&.todo {
|
||||
background: rgba(56, 189, 248, 0.14);
|
||||
color: #38bdf8;
|
||||
}
|
||||
|
||||
&.ready {
|
||||
@@ -502,9 +496,24 @@ async function handleAssign() {
|
||||
color: $warning;
|
||||
}
|
||||
|
||||
&.triage, &.archived {
|
||||
background: rgba(128, 128, 128, 0.12);
|
||||
color: $text-muted;
|
||||
&.running {
|
||||
background: rgba(var(--accent-primary-rgb), 0.12);
|
||||
color: $accent-primary;
|
||||
}
|
||||
|
||||
&.blocked {
|
||||
background: rgba(var(--error-rgb), 0.12);
|
||||
color: $error;
|
||||
}
|
||||
|
||||
&.done {
|
||||
background: rgba(var(--success-rgb), 0.12);
|
||||
color: $success;
|
||||
}
|
||||
|
||||
&.archived {
|
||||
background: rgba(100, 116, 139, 0.14);
|
||||
color: #94a3b8;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user