Epics table: barra de progreso (% HUs completadas) + removida columna Asignado
- epicProgress computed: agrupa HUs por _epicCode, calcula % completadas - getEpicProgress(epic): resuelve progreso para cada épica - Barra visual con colores: verde=100%, azul=>0%, gris=0%
This commit is contained in:
@@ -539,6 +539,32 @@ watch(
|
|||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ─── Epic progress bar ───────────────────────────────
|
||||||
|
const epicProgress = computed(() => {
|
||||||
|
const map = new Map<string, { total: number; done: number }>()
|
||||||
|
for (const hu of workItems.userStories) {
|
||||||
|
const epicCode = hu._epicCode || ''
|
||||||
|
if (!epicCode) continue
|
||||||
|
if (!map.has(epicCode)) map.set(epicCode, { total: 0, done: 0 })
|
||||||
|
const entry = map.get(epicCode)!
|
||||||
|
entry.total++
|
||||||
|
const s = String(hu.status ?? '').toLowerCase()
|
||||||
|
if (['done', 'completed', 'closed', 'finalizado', '5', 'true'].includes(s)) {
|
||||||
|
entry.done++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const result: Record<string, number> = {}
|
||||||
|
for (const [code, { total, done }] of map) {
|
||||||
|
result[code] = total > 0 ? Math.round((done / total) * 100) : 0
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
|
||||||
|
function getEpicProgress(epic: any): number {
|
||||||
|
const code = epic._epicCode
|
||||||
|
return code ? epicProgress.value[code] ?? 0 : 0
|
||||||
|
}
|
||||||
|
|
||||||
const statusVariant = (status: unknown) => {
|
const statusVariant = (status: unknown) => {
|
||||||
const s = String(status ?? '').toLowerCase()
|
const s = String(status ?? '').toLowerCase()
|
||||||
if (['done', 'completed', 'closed', 'finalizado'].includes(s)) return 'secondary'
|
if (['done', 'completed', 'closed', 'finalizado'].includes(s)) return 'secondary'
|
||||||
@@ -771,8 +797,7 @@ const statusLabel = (status: unknown) => {
|
|||||||
<TableHead class="w-[60px]">{{ t('users.role') }}</TableHead>
|
<TableHead class="w-[60px]">{{ t('users.role') }}</TableHead>
|
||||||
<TableHead>{{ t('dashboard.title') }}</TableHead>
|
<TableHead>{{ t('dashboard.title') }}</TableHead>
|
||||||
<TableHead class="w-[60px] text-center">Desc</TableHead>
|
<TableHead class="w-[60px] text-center">Desc</TableHead>
|
||||||
<TableHead class="w-[90px] text-center">{{ t('dashboard.assignedTo') }}</TableHead>
|
<TableHead class="w-[140px]">Progreso</TableHead>
|
||||||
<TableHead class="w-[100px]">{{ t('dashboard.status') }}</TableHead>
|
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
@@ -792,9 +817,17 @@ const statusLabel = (status: unknown) => {
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
<span v-else class="text-xs text-muted-foreground/40">—</span>
|
<span v-else class="text-xs text-muted-foreground/40">—</span>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell class="text-center text-xs text-muted-foreground">—</TableCell>
|
<TableCell class="text-xs">
|
||||||
<TableCell>
|
<div class="flex items-center gap-2">
|
||||||
<Badge :variant="statusVariant(epic.status || '')" class="text-xs">{{ statusLabel(epic.status || '') }}</Badge>
|
<div class="flex-1 h-2 rounded-full bg-muted overflow-hidden">
|
||||||
|
<div
|
||||||
|
class="h-full rounded-full transition-all duration-300"
|
||||||
|
:class="getEpicProgress(epic) >= 100 ? 'bg-green-500' : getEpicProgress(epic) > 0 ? 'bg-primary' : ''"
|
||||||
|
:style="{ width: getEpicProgress(epic) + '%' }"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span class="font-mono text-[10px] text-muted-foreground w-8 text-right">{{ getEpicProgress(epic) }}%</span>
|
||||||
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableBody>
|
</TableBody>
|
||||||
|
|||||||
Reference in New Issue
Block a user