fix: orden dashboard + i18n HuDrafts + tipo Epic

- DashboardView: borradores antes de épicas, HuDrafts antes de tabla HUs
- DashboardView: skeletons eliminados
- DashboardView: fix duplicate const project (causaba ReferenceError)
- HuDrafts: i18n para tipos (Epic, Feature, Task, HU, Bug)
- HuDrafts: tipo Epic (E) agregado al selector
- i18n: +hierarchy section con labels en es/en
This commit is contained in:
2026-05-28 15:30:38 -05:00
parent 292b9844c6
commit dd9f76be6f
4 changed files with 105 additions and 122 deletions
+80 -119
View File
@@ -14,7 +14,6 @@ import { kappa } from '@/services/kappa-api'
import { generateAndSavePlan, getQAPlans, type HUQAPlan } from '@/services/qa-analyzer'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
import { Skeleton } from '@/components/ui/skeleton'
import {
Table,
TableBody,
@@ -28,6 +27,8 @@ const { t } = useI18n()
const projects = useProjectsStore()
const workItems = useWorkItemsStore()
const project = computed(() => projects.selected)
const emit = defineEmits<{
'navigate-settings': []
}>()
@@ -195,8 +196,6 @@ async function runAnalysis() {
}
}
const project = computed(() => projects.selected)
watch(
() => projects.selectedId,
async (id) => {
@@ -340,122 +339,6 @@ const statusLabel = (status: unknown) => {
</CardContent>
</Card>
<!-- Loading -->
<template v-if="workItems.loading">
<Skeleton class="h-8 w-1/3" />
<div class="space-y-2">
<Skeleton v-for="i in 4" :key="i" class="h-12 w-full" />
</div>
</template>
<!-- Epics -->
<template v-else-if="workItems.epics.length > 0">
<div>
<h3 id="dashboard-epics-heading" class="text-sm font-semibold text-muted-foreground uppercase tracking-wider mb-3">
{{ t('dashboard.epicsCount', { count: workItems.totalEpics }) }}
</h3>
<div class="space-y-2">
<Card
v-for="epic in workItems.epics"
:key="epic.id"
class="bg-gradient-to-t from-primary/5 to-card shadow-xs dark:bg-card"
>
<CardHeader class="p-4 pb-2">
<div class="flex items-start justify-between gap-2">
<div class="flex items-center gap-2 min-w-0">
<span
class="inline-flex items-center rounded px-1.5 py-0.5 text-[10px] font-bold"
:class="getTypeColor(epic._itemType)"
>
{{ getTypeIcon(epic._itemType) }} {{ getTypeLabel(epic._itemType) }}
</span>
<span class="font-mono text-xs text-muted-foreground flex-shrink-0">
{{ epic.code || `EP-${epic.id}` }}
</span>
<CardTitle class="text-sm truncate">{{ epic._cleanName || epic.name || epic.title || t('dashboard.epicFallback', { id: epic.id }) }}</CardTitle>
</div>
<Badge :variant="statusVariant(epic.status || '')" class="text-xs flex-shrink-0">
{{ statusLabel(epic.status || '') }}
</Badge>
</div>
</CardHeader>
<CardContent v-if="epic.description" class="p-4 pt-0">
<p class="text-xs text-muted-foreground line-clamp-2">
{{ epic.description }}
</p>
</CardContent>
</Card>
</div>
</div>
</template>
<!-- HUs Table -->
<Card id="dashboard-hus-table">
<CardHeader class="flex flex-row items-center justify-between pb-2">
<CardTitle class="text-sm font-medium">{{ t('dashboard.userStoriesTitle') }}</CardTitle>
<Badge variant="outline" class="text-xs">{{ t('dashboard.husCount', { count: workItems.userStories.length }) }}</Badge>
</CardHeader>
<CardContent>
<Table v-if="workItems.userStories.length > 0">
<TableHeader>
<TableRow>
<TableHead class="w-[80px]">{{ t('dashboard.code') }}</TableHead>
<TableHead class="w-[60px]">{{ t('users.role') }}</TableHead>
<TableHead>{{ t('dashboard.title') }}</TableHead>
<TableHead class="w-[110px]">{{ t('dashboard.status') }}</TableHead>
<TableHead class="w-[90px] text-right">{{ t('dashboard.priority') }}</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow v-for="hu in workItems.userStories" :key="hu.id">
<TableCell class="font-mono text-xs text-muted-foreground">
{{ hu.code || `HU-${hu.id}` }}
</TableCell>
<TableCell>
<span
class="inline-flex items-center rounded px-1 py-0.5 text-[10px] font-bold"
:class="getTypeColor(hu._itemType)"
>
{{ getTypeLabel(hu._itemType) }}
</span>
</TableCell>
<TableCell class="text-sm max-w-[280px] truncate flex items-center gap-1">
<AlertTriangle
v-if="hu.has_impairment"
class="size-3.5 text-amber-500 flex-shrink-0"
title="Tiene impedimentos pendientes"
/>
<span class="truncate">{{ hu._cleanTitle || hu.title }}</span>
<span
v-if="hu._criteriaList?.length"
class="group relative inline-flex flex-shrink-0 cursor-help"
>
<Info class="size-3.5 text-muted-foreground hover:text-foreground transition-colors" />
<div class="absolute bottom-full left-0 mb-2 w-72 p-3 rounded-lg border bg-popover text-popover-foreground text-xs shadow-md opacity-0 group-hover:opacity-100 transition-opacity z-50 pointer-events-none">
<p class="font-semibold mb-1.5 text-[11px] uppercase tracking-wider text-muted-foreground">Criterios de aceptación</p>
<ol class="list-decimal list-inside space-y-1 text-[11px]">
<li v-for="(c, i) in hu._criteriaList" :key="i">{{ c }}</li>
</ol>
</div>
</span>
</TableCell>
<TableCell>
<Badge :variant="statusVariant(hu.status || '')" class="text-xs capitalize">
{{ statusLabel(hu.status || '') }}
</Badge>
</TableCell>
<TableCell class="text-right text-xs text-muted-foreground">
{{ hu.priority || '—' }}
</TableCell>
</TableRow>
</TableBody>
</Table>
<p v-else class="text-xs text-muted-foreground/50 italic text-center py-4">
{{ t('dashboard.noUserStories') }}
</p>
</CardContent>
</Card>
<!-- Borradores (web) -->
<Card v-if="drafts.length > 0" id="dashboard-drafts" class="border-dashed">
<CardHeader class="pb-3 flex flex-row items-center justify-between">
@@ -508,8 +391,86 @@ const statusLabel = (status: unknown) => {
</CardContent>
</Card>
<!-- Epics -->
<template v-if="workItems.epics.length > 0">
<div>
<h3 id="dashboard-epics-heading" class="text-sm font-semibold text-muted-foreground uppercase tracking-wider mb-3">
{{ t('dashboard.epicsCount', { count: workItems.totalEpics }) }}
</h3>
<div class="space-y-2">
<Card
v-for="epic in workItems.epics"
:key="epic.id"
class="bg-gradient-to-t from-primary/5 to-card shadow-xs dark:bg-card"
>
<CardHeader class="p-4 pb-2">
<div class="flex items-start justify-between gap-2">
<div class="flex items-center gap-2 min-w-0">
<span
class="inline-flex items-center rounded px-1.5 py-0.5 text-[10px] font-bold"
:class="getTypeColor(epic._itemType)"
>
{{ getTypeIcon(epic._itemType) }} {{ getTypeLabel(epic._itemType) }}
</span>
<span class="font-mono text-xs text-muted-foreground flex-shrink-0">{{ epic.code || `EP-${epic.id}` }}</span>
<CardTitle class="text-sm truncate">{{ epic._cleanName || epic.name || epic.title || t('dashboard.epicFallback', { id: epic.id }) }}</CardTitle>
</div>
<Badge :variant="statusVariant(epic.status || '')" class="text-xs flex-shrink-0">{{ statusLabel(epic.status || '') }}</Badge>
</div>
</CardHeader>
<CardContent v-if="epic.description" class="p-4 pt-0">
<p class="text-xs text-muted-foreground line-clamp-2">{{ epic.description }}</p>
</CardContent>
</Card>
</div>
</div>
</template>
<!-- Borradores (Tauri) -->
<HuDrafts v-if="project" :initiative-id="project.id" />
<!-- HUs Table -->
<Card id="dashboard-hus-table">
<CardHeader class="flex flex-row items-center justify-between pb-2">
<CardTitle class="text-sm font-medium">{{ t('dashboard.userStoriesTitle') }}</CardTitle>
<Badge variant="outline" class="text-xs">{{ t('dashboard.husCount', { count: workItems.userStories.length }) }}</Badge>
</CardHeader>
<CardContent>
<Table v-if="workItems.userStories.length > 0">
<TableHeader>
<TableRow>
<TableHead class="w-[80px]">{{ t('dashboard.code') }}</TableHead>
<TableHead class="w-[60px]">{{ t('users.role') }}</TableHead>
<TableHead>{{ t('dashboard.title') }}</TableHead>
<TableHead class="w-[110px]">{{ t('dashboard.status') }}</TableHead>
<TableHead class="w-[90px] text-right">{{ t('dashboard.priority') }}</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow v-for="hu in workItems.userStories" :key="hu.id">
<TableCell class="font-mono text-xs text-muted-foreground">{{ hu.code || `HU-${hu.id}` }}</TableCell>
<TableCell>
<span class="inline-flex items-center rounded px-1 py-0.5 text-[10px] font-bold" :class="getTypeColor(hu._itemType)">{{ getTypeLabel(hu._itemType) }}</span>
</TableCell>
<TableCell class="text-sm max-w-[280px] truncate flex items-center gap-1">
<AlertTriangle v-if="hu.has_impairment" class="size-3.5 text-amber-500 flex-shrink-0" title="Tiene impedimentos pendientes" />
<span class="truncate">{{ hu._cleanTitle || hu.title }}</span>
<span v-if="hu._criteriaList?.length" class="group relative inline-flex flex-shrink-0 cursor-help">
<Info class="size-3.5 text-muted-foreground hover:text-foreground transition-colors" />
<div class="absolute bottom-full left-0 mb-2 w-72 p-3 rounded-lg border bg-popover text-popover-foreground text-xs shadow-md opacity-0 group-hover:opacity-100 transition-opacity z-50 pointer-events-none">
<p class="font-semibold mb-1.5 text-[11px] uppercase tracking-wider text-muted-foreground">Criterios de aceptación</p>
<ol class="list-decimal list-inside space-y-1 text-[11px]"><li v-for="(c, i) in hu._criteriaList" :key="i">{{ c }}</li></ol>
</div>
</span>
</TableCell>
<TableCell><Badge :variant="statusVariant(hu.status || '')" class="text-xs capitalize">{{ statusLabel(hu.status || '') }}</Badge></TableCell>
<TableCell class="text-right text-xs text-muted-foreground">{{ hu.priority || '—' }}</TableCell>
</TableRow>
</TableBody>
</Table>
<p v-else class="text-xs text-muted-foreground/50 italic text-center py-4">{{ t('dashboard.noUserStories') }}</p>
</CardContent>
</Card>
</div>
<div v-else class="flex flex-1 items-center justify-center">