feat: ship agent progress visibility
This commit is contained in:
@@ -13,7 +13,7 @@
|
||||
* - Waiting section for Iris overview
|
||||
*/
|
||||
import { computed, onBeforeUnmount, onMounted, onUnmounted, reactive, ref, watch } from 'vue'
|
||||
import { Plus, X, CalendarDays, Clock3, ExternalLink, Link2, ListChecks, Save, AlertTriangle, Eye, Bot, ShieldBan } from '@lucide/vue'
|
||||
import { Plus, X, CalendarDays, Clock3, ExternalLink, Link2, ListChecks, Save, AlertTriangle, Eye, Bot, ShieldBan, MessageSquareText } from '@lucide/vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useAuthStore } from '../stores/auth'
|
||||
import { useTaskStore } from '../stores/tasks'
|
||||
@@ -238,6 +238,22 @@ function hoursSince(dateStr: string): number {
|
||||
return Math.round((now - then) / 3600000)
|
||||
}
|
||||
|
||||
function relativeTime(date?: string | null): string {
|
||||
if (!date) return 'keine Updates'
|
||||
const diffMs = Date.now() - new Date(date).getTime()
|
||||
const mins = Math.max(0, Math.round(diffMs / 60000))
|
||||
if (mins < 1) return 'gerade eben'
|
||||
if (mins < 60) return `vor ${mins} min`
|
||||
const hours = Math.round(mins / 60)
|
||||
if (hours < 24) return `vor ${hours} h`
|
||||
const days = Math.round(hours / 24)
|
||||
return `vor ${days} d`
|
||||
}
|
||||
|
||||
function activityHint(task: BoardTask): string {
|
||||
return task.lastActivityMessage?.trim() || (task.expectedFrom ? `Wartet auf ${expectedFromLabel(task.expectedFrom)}` : 'Noch kein relevanter Progress-Status')
|
||||
}
|
||||
|
||||
/* ── Task Navigation ───────────────────────────── */
|
||||
function navigateToTask(taskId: string) {
|
||||
router.push('/tasks/' + taskId)
|
||||
@@ -401,9 +417,12 @@ onUnmounted(() => {
|
||||
Warte auf Iris <span class="section-count">{{ waitingForIrisCount }}</span>
|
||||
</div>
|
||||
<div v-if="taskStore.waitingForIrisTasks.length === 0" class="iris-empty">Keine Tasks</div>
|
||||
<div v-for="t in taskStore.waitingForIrisTasks" :key="t.id" class="iris-task-row">
|
||||
<span class="iris-task-title">{{ t.title }}</span>
|
||||
<span class="iris-task-meta">{{ stateLabel(t.state) }}</span>
|
||||
<div v-for="t in taskStore.waitingForIrisTasks" :key="t.id" class="iris-task-row progress-row">
|
||||
<div>
|
||||
<span class="iris-task-title">{{ t.title }}</span>
|
||||
<div class="iris-task-progress">{{ activityHint(t) }}</div>
|
||||
</div>
|
||||
<span class="iris-task-meta">{{ relativeTime(t.lastActivityAt ?? t.updatedAt) }}</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -413,9 +432,12 @@ onUnmounted(() => {
|
||||
Warte auf Bao <span class="section-count">{{ waitingForBaoCount }}</span>
|
||||
</div>
|
||||
<div v-if="taskStore.waitingForBaoTasks.length === 0" class="iris-empty">Keine Tasks</div>
|
||||
<div v-for="t in taskStore.waitingForBaoTasks" :key="t.id" class="iris-task-row">
|
||||
<span class="iris-task-title">{{ t.title }}</span>
|
||||
<span class="iris-task-meta">{{ stateLabel(t.state) }}</span>
|
||||
<div v-for="t in taskStore.waitingForBaoTasks" :key="t.id" class="iris-task-row progress-row">
|
||||
<div>
|
||||
<span class="iris-task-title">{{ t.title }}</span>
|
||||
<div class="iris-task-progress">{{ activityHint(t) }}</div>
|
||||
</div>
|
||||
<span class="iris-task-meta">{{ relativeTime(t.lastActivityAt ?? t.updatedAt) }}</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -425,8 +447,11 @@ onUnmounted(() => {
|
||||
Warte auf andere <span class="section-count">{{ taskStore.waitingForOthersTasks.length }}</span>
|
||||
</div>
|
||||
<div v-if="taskStore.waitingForOthersTasks.length === 0" class="iris-empty">Keine Tasks</div>
|
||||
<div v-for="t in taskStore.waitingForOthersTasks" :key="t.id" class="iris-task-row">
|
||||
<span class="iris-task-title">{{ t.title }}</span>
|
||||
<div v-for="t in taskStore.waitingForOthersTasks" :key="t.id" class="iris-task-row progress-row">
|
||||
<div>
|
||||
<span class="iris-task-title">{{ t.title }}</span>
|
||||
<div class="iris-task-progress">{{ activityHint(t) }}</div>
|
||||
</div>
|
||||
<span class="iris-task-meta">{{ expectedFromLabel(t.expectedFrom) }}</span>
|
||||
</div>
|
||||
</section>
|
||||
@@ -437,8 +462,11 @@ onUnmounted(() => {
|
||||
Stale Tasks <span class="section-count stale-count">{{ staleCount }}</span>
|
||||
</div>
|
||||
<div v-if="taskStore.staleTasksList.length === 0" class="iris-empty">Keine stale Tasks</div>
|
||||
<div v-for="t in taskStore.staleTasksList" :key="t.id" class="iris-task-row stale-row">
|
||||
<span class="iris-task-title">{{ t.title }}</span>
|
||||
<div v-for="t in taskStore.staleTasksList" :key="t.id" class="iris-task-row stale-row progress-row">
|
||||
<div>
|
||||
<span class="iris-task-title">{{ t.title }}</span>
|
||||
<div class="iris-task-progress">{{ activityHint(t) }}</div>
|
||||
</div>
|
||||
<span class="iris-task-meta stale-meta">{{ hoursSince(t.updatedAt) }}h offen</span>
|
||||
</div>
|
||||
</section>
|
||||
@@ -493,7 +521,8 @@ onUnmounted(() => {
|
||||
</div>
|
||||
<div class="card-title">{{ task.title }}</div>
|
||||
<div v-if="task.detail" class="card-preview">{{ task.detail }}</div>
|
||||
<div class="card-meta">{{ new Date(task.createdAt).toLocaleDateString('de-DE') }}</div>
|
||||
<div v-if="task.isAgentTask" class="card-progress-hint">{{ activityHint(task) }}</div>
|
||||
<div class="card-meta">Update {{ relativeTime(task.lastActivityAt ?? task.updatedAt) }}</div>
|
||||
</button>
|
||||
<div v-if="!taskStore.board.offen.length" class="empty-col">Keine Aufgaben</div>
|
||||
</div>
|
||||
@@ -541,7 +570,8 @@ onUnmounted(() => {
|
||||
</div>
|
||||
<div class="card-title">{{ task.title }}</div>
|
||||
<div v-if="task.detail" class="card-preview">{{ task.detail }}</div>
|
||||
<div class="card-meta">{{ new Date(task.createdAt).toLocaleDateString('de-DE') }}</div>
|
||||
<div v-if="task.isAgentTask" class="card-progress-hint">{{ activityHint(task) }}</div>
|
||||
<div class="card-meta">Update {{ relativeTime(task.lastActivityAt ?? task.updatedAt) }}</div>
|
||||
</button>
|
||||
<div v-if="!taskStore.board.inProgress.length" class="empty-col">Keine Aufgaben</div>
|
||||
</div>
|
||||
@@ -589,7 +619,8 @@ onUnmounted(() => {
|
||||
</div>
|
||||
<div class="card-title">{{ task.title }}</div>
|
||||
<div v-if="task.detail" class="card-preview">{{ task.detail }}</div>
|
||||
<div class="card-meta">{{ new Date(task.createdAt).toLocaleDateString('de-DE') }}</div>
|
||||
<div v-if="task.isAgentTask" class="card-progress-hint">{{ activityHint(task) }}</div>
|
||||
<div class="card-meta">Update {{ relativeTime(task.lastActivityAt ?? task.updatedAt) }}</div>
|
||||
</button>
|
||||
<div v-if="!taskStore.board.delegated.length" class="empty-col">Keine delegierten Aufgaben</div>
|
||||
</div>
|
||||
@@ -637,7 +668,8 @@ onUnmounted(() => {
|
||||
</div>
|
||||
<div class="card-title">{{ task.title }}</div>
|
||||
<div v-if="task.detail" class="card-preview">{{ task.detail }}</div>
|
||||
<div class="card-meta">{{ new Date(task.createdAt).toLocaleDateString('de-DE') }}</div>
|
||||
<div v-if="task.isAgentTask" class="card-progress-hint">{{ activityHint(task) }}</div>
|
||||
<div class="card-meta">Update {{ relativeTime(task.lastActivityAt ?? task.updatedAt) }}</div>
|
||||
</button>
|
||||
<div v-if="!taskStore.board.review.length" class="empty-col">Keine Aufgaben</div>
|
||||
</div>
|
||||
@@ -680,7 +712,8 @@ onUnmounted(() => {
|
||||
</div>
|
||||
<div class="card-title">{{ task.title }}</div>
|
||||
<div v-if="task.detail" class="card-preview">{{ task.detail }}</div>
|
||||
<div class="card-meta">{{ new Date(task.createdAt).toLocaleDateString('de-DE') }}</div>
|
||||
<div v-if="task.isAgentTask" class="card-progress-hint">{{ activityHint(task) }}</div>
|
||||
<div class="card-meta">Update {{ relativeTime(task.lastActivityAt ?? task.updatedAt) }}</div>
|
||||
</button>
|
||||
<div v-if="!taskStore.board.done.length" class="empty-col">Keine Aufgaben</div>
|
||||
</div>
|
||||
@@ -728,7 +761,7 @@ onUnmounted(() => {
|
||||
</div>
|
||||
<div class="card-title">{{ task.title }}</div>
|
||||
<div v-if="task.detail" class="card-preview">{{ task.detail }}</div>
|
||||
<div class="card-meta">{{ new Date(task.createdAt).toLocaleDateString('de-DE') }}</div>
|
||||
<div class="card-meta">Update {{ relativeTime(task.lastActivityAt ?? task.updatedAt) }}</div>
|
||||
</button>
|
||||
<div v-if="!taskStore.board.blocked.length" class="empty-col">Keine Blockierer</div>
|
||||
</div>
|
||||
@@ -818,6 +851,10 @@ onUnmounted(() => {
|
||||
<span v-if="selectedTask.expectedFrom" class="meta-expected">⏳ Erwartet: {{ selectedTask.expectedFrom }}</span>
|
||||
<span><Clock3 :size="13" /> Aktualisiert {{ formatDate(selectedTask.updatedAt, true) }}</span>
|
||||
<span><CalendarDays :size="13" /> Erstellt {{ formatDate(selectedTask.createdAt) }}</span>
|
||||
<span v-if="selectedTask.isAgentTask"><MessageSquareText :size="13" /> Letzter Status {{ relativeTime(selectedTask.lastActivityAt ?? selectedTask.updatedAt) }}</span>
|
||||
</div>
|
||||
<div v-if="selectedTask.isAgentTask" class="detail-progress-banner">
|
||||
<strong>Letzter Fortschritt:</strong> {{ activityHint(selectedTask) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1003,7 +1040,9 @@ onUnmounted(() => {
|
||||
.iris-empty { font-size: 11px; color: var(--tx-3); font-style: italic; padding: 8px; text-align: center; }
|
||||
.iris-task-row { padding: 6px 8px; border-bottom: 1px solid var(--line); display: flex; align-items: center; justify-content: space-between; gap: 6px; }
|
||||
.iris-task-row:last-child { border-bottom: none; }
|
||||
.iris-task-title { font-size: 11.5px; font-weight: 500; color: var(--tx); }
|
||||
.progress-row { align-items: flex-start; }
|
||||
.iris-task-title { font-size: 11.5px; font-weight: 500; color: var(--tx); display: block; }
|
||||
.iris-task-progress { margin-top: 4px; font-size: 10px; color: var(--tx-3); line-height: 1.35; }
|
||||
.iris-task-meta { font-size: 10px; color: var(--tx-3); white-space: nowrap; }
|
||||
.stale-row { background: rgba(244,63,94,.05); border-radius: 4px; }
|
||||
.stale-meta { color: #fda4af; font-weight: 600; }
|
||||
@@ -1041,6 +1080,7 @@ select:disabled { opacity: .45; cursor: not-allowed; }
|
||||
.assignee-bao { background: rgba(59, 130, 246, .12); color: #60a5fa; }
|
||||
.card-title { font-size: 12.5px; font-weight: 600; color: var(--tx); line-height: 1.4; word-break: break-word; font-family: 'Manrope', sans-serif; }
|
||||
.card-preview { margin-top: 6px; font-size: 11px; line-height: 1.45; color: var(--tx-2); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
|
||||
.card-progress-hint { margin-top: 7px; font-size: 10.5px; color: var(--tx-2); line-height: 1.4; padding: 6px 8px; border-radius: 8px; background: rgba(124,108,255,.07); border: 1px solid rgba(124,108,255,.12); }
|
||||
.card-meta { font-family: 'JetBrains Mono', monospace; font-size: 10px; color: var(--tx-3); margin-top: 5px; font-variant-numeric: tabular-nums; }
|
||||
.empty-col, .detail-empty { display: flex; align-items: center; justify-content: center; padding: 24px 12px; font-size: 11px; color: var(--tx-3); font-style: italic; font-family: 'Manrope', sans-serif; }
|
||||
|
||||
@@ -1092,6 +1132,7 @@ select:disabled { opacity: .45; cursor: not-allowed; }
|
||||
.detail-meta-row span { display: inline-flex; align-items: center; gap: 5px; }
|
||||
.meta-agent-tag { color: #c084fc; }
|
||||
.meta-expected { color: #a78bfa; }
|
||||
.detail-progress-banner { padding: 10px 12px; border-radius: 12px; background: rgba(124,108,255,.08); border: 1px solid rgba(124,108,255,.14); color: var(--tx-2); font-size: 12px; }
|
||||
.detail-section { background: rgba(255,255,255,.02); border: 1px solid var(--line); border-radius: 16px; padding: 16px; }
|
||||
.detail-section-header, .sidebar-heading { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; color: var(--tx-2); font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .06em; }
|
||||
.detail-textarea { width: 100%; min-height: 180px; border-radius: 12px; border: 1px solid var(--line); background: rgba(10,9,24,.55); color: var(--tx); padding: 14px; font-size: 14px; line-height: 1.6; outline: none; box-sizing: border-box; }
|
||||
|
||||
Reference in New Issue
Block a user