Revert: stats cards a 5 originales + sidecar Python pospuesto
This commit is contained in:
+44
-103
@@ -13,7 +13,7 @@ import HuDrafts from '@/components/HuDrafts.vue'
|
|||||||
import AiProjectChat from '@/components/AiProjectChat.vue'
|
import AiProjectChat from '@/components/AiProjectChat.vue'
|
||||||
import PrioritizerCard from '@/components/PrioritizerCard.vue'
|
import PrioritizerCard from '@/components/PrioritizerCard.vue'
|
||||||
import SCurveChart from '@/components/SCurveChart.vue'
|
import SCurveChart from '@/components/SCurveChart.vue'
|
||||||
import { generateReportDocx } from '@/services/report-export'
|
// import { generateReportDocx } from '@/services/report-export' // Sidecar Python pending
|
||||||
import { analyzeProjectEpics, analyzeProjectHUs, saveEpicDrafts, saveHUDrafts } from '@/services/project-analyzer'
|
import { analyzeProjectEpics, analyzeProjectHUs, saveEpicDrafts, saveHUDrafts } from '@/services/project-analyzer'
|
||||||
import { getDrafts, deleteDraft, type HuDraftRecord } from '@/services/hu-drafts-db'
|
import { getDrafts, deleteDraft, type HuDraftRecord } from '@/services/hu-drafts-db'
|
||||||
import { kappa } from '@/services/kappa-api'
|
import { kappa } from '@/services/kappa-api'
|
||||||
@@ -411,57 +411,7 @@ async function discardDraft(id: string) {
|
|||||||
await loadDrafts()
|
await loadDrafts()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Export report ──────────────────────────────────
|
// ─── Export report (pending Python sidecar) ────────
|
||||||
async function exportReport() {
|
|
||||||
if (!project.value) return
|
|
||||||
showNotif('info', 'Generando informe DOCX...')
|
|
||||||
try {
|
|
||||||
const doneCount = workItems.userStories.filter(hu => {
|
|
||||||
const s = String(hu.status ?? '').toLowerCase()
|
|
||||||
return ['done', 'completed', 'closed', 'finalizado', '5', '6', '7', 'qa-client', 'ready to deploy'].includes(s)
|
|
||||||
}).length
|
|
||||||
|
|
||||||
const blob = await generateReportDocx({
|
|
||||||
projectId: project.value.id,
|
|
||||||
projectName: project.value.name || '',
|
|
||||||
epicCount: workItems.totalEpics,
|
|
||||||
huCount: workItems.totalHUs,
|
|
||||||
inProgressCount: workItems.inProgressHUs,
|
|
||||||
doneCount,
|
|
||||||
blockedCount: workItems.userStories.filter(hu => {
|
|
||||||
const s = String(hu.status ?? '').toLowerCase()
|
|
||||||
return ['blocked', 'bloqueado'].includes(s)
|
|
||||||
}).length,
|
|
||||||
epics: workItems.epics,
|
|
||||||
hus: workItems.userStories,
|
|
||||||
totalSessions: workItems.totalSessions,
|
|
||||||
metrics: {
|
|
||||||
totalSpPlanned: workItems.userStories.reduce((s, h) => s + (h.story_points || 0), 0),
|
|
||||||
totalSpCompleted: workItems.userStories.filter(hu => {
|
|
||||||
const s = String(hu.status ?? '').toLowerCase()
|
|
||||||
return ['done', 'completed', 'closed', 'finalizado', '5', '6', '7', 'qa-client', 'ready to deploy'].includes(s)
|
|
||||||
}).reduce((s, h) => s + (h.story_points || 0), 0),
|
|
||||||
velocityPerWeek: 0,
|
|
||||||
spi: 1,
|
|
||||||
estimatedEndDate: '',
|
|
||||||
clientBlockedHours: 0,
|
|
||||||
totalBlockedHours: 0,
|
|
||||||
},
|
|
||||||
generatedAt: new Date().toLocaleDateString('es-CO', { year: 'numeric', month: 'long', day: 'numeric' }),
|
|
||||||
})
|
|
||||||
|
|
||||||
const url = URL.createObjectURL(blob)
|
|
||||||
const a = document.createElement('a')
|
|
||||||
a.href = url
|
|
||||||
a.download = `informe_${project.value.name?.replace(/[^a-z0-9]/gi, '_').toLowerCase() || 'proyecto'}.docx`
|
|
||||||
a.click()
|
|
||||||
URL.revokeObjectURL(url)
|
|
||||||
showNotif('success', 'Informe descargado')
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error('[Alpha] Export error:', e)
|
|
||||||
showNotif('error', `Error al generar informe: ${e.message}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─── Project analysis — Two-phase ─────────────────────────
|
// ─── Project analysis — Two-phase ─────────────────────────
|
||||||
const phase = ref<'idle' | 'epics' | 'hus' | 'done'>('idle')
|
const phase = ref<'idle' | 'epics' | 'hus' | 'done'>('idle')
|
||||||
@@ -652,56 +602,57 @@ const statusLabel = (status: unknown) => {
|
|||||||
</h1>
|
</h1>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<Badge v-if="project.key" variant="outline" class="text-xs">{{ project.key }}</Badge>
|
<Badge v-if="project.key" variant="outline" class="text-xs">{{ project.key }}</Badge>
|
||||||
<button
|
<!-- Export button pending Python sidecar
|
||||||
class="ml-auto text-[11px] text-muted-foreground hover:text-foreground transition-colors flex items-center gap-1"
|
<button class="ml-auto text-[11px] text-muted-foreground hover:text-foreground transition-colors flex items-center gap-1" title="Exportar informe DOCX (próximamente)">
|
||||||
@click="exportReport"
|
<FileText class="size-3.5" /> Exportar
|
||||||
title="Exportar informe DOCX"
|
</button> -->
|
||||||
>
|
|
||||||
<FileText class="size-3.5" />
|
|
||||||
Exportar
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Stats: 4 cards -->
|
<!-- Stats -->
|
||||||
<div id="dashboard-stats" class="grid gap-3 @xl:grid-cols-2 @3xl:grid-cols-4">
|
<div id="dashboard-stats" class="grid gap-3 @xl:grid-cols-2 @3xl:grid-cols-5">
|
||||||
<Card id="dashboard-stats-epics" class="bg-gradient-to-t from-primary/5 to-card shadow-xs dark:bg-card">
|
<Card id="dashboard-stats-epics" class="bg-gradient-to-t from-primary/5 to-card shadow-xs dark:bg-card">
|
||||||
<CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
|
<CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||||
<CardTitle class="text-xs font-medium">{{ t('dashboard.epics') }} · {{ t('dashboard.hus') }} · {{ t('dashboard.inProgress') }}</CardTitle>
|
<CardTitle class="text-sm font-medium">{{ t('dashboard.epics') }}</CardTitle>
|
||||||
<Layers class="size-3.5 text-muted-foreground" />
|
<Layers class="size-4 text-muted-foreground" />
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent class="flex items-center gap-4">
|
<CardContent>
|
||||||
<div class="flex flex-col items-center">
|
<div class="text-2xl font-bold">{{ workItems.totalEpics }}</div>
|
||||||
<span class="text-xl font-bold">{{ workItems.totalEpics }}</span>
|
<p class="text-xs text-muted-foreground">{{ t('dashboard.epicsSubtitle') }}</p>
|
||||||
<span class="text-[10px] text-muted-foreground">{{ t('dashboard.epics') }}</span>
|
</CardContent>
|
||||||
</div>
|
</Card>
|
||||||
<div class="flex flex-col items-center">
|
|
||||||
<span id="dashboard-stats-hus-count" class="text-xl font-bold">{{ workItems.totalHUs }}</span>
|
<Card id="dashboard-stats-hus" class="bg-gradient-to-t from-primary/5 to-card shadow-xs dark:bg-card">
|
||||||
<span class="text-[10px] text-muted-foreground">HUs</span>
|
<CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||||
</div>
|
<CardTitle class="text-sm font-medium">{{ t('dashboard.hus') }}</CardTitle>
|
||||||
<div class="flex flex-col items-center">
|
<FileText class="size-4 text-muted-foreground" />
|
||||||
<span class="text-xl font-bold">{{ workItems.inProgressHUs }}</span>
|
</CardHeader>
|
||||||
<span class="text-[10px] text-muted-foreground">En prog.</span>
|
<CardContent>
|
||||||
</div>
|
<div id="dashboard-stats-hus-count" class="text-2xl font-bold">{{ workItems.totalHUs }}</div>
|
||||||
<div class="flex-1 h-10 flex items-end gap-0.5">
|
<p class="text-xs text-muted-foreground">{{ t('dashboard.husSubtitle') }}</p>
|
||||||
<div v-for="(val, i) in [workItems.totalEpics, workItems.totalHUs, workItems.inProgressHUs]" :key="i"
|
</CardContent>
|
||||||
class="flex-1 rounded-t-sm"
|
</Card>
|
||||||
:class="['bg-primary/20', 'bg-primary/40', 'bg-primary/60'][i]"
|
|
||||||
:style="{ height: Math.max(8, (val / Math.max(workItems.totalHUs, 1)) * 100) + '%' }"
|
<Card id="dashboard-stats-inprogress" class="bg-gradient-to-t from-primary/5 to-card shadow-xs dark:bg-card">
|
||||||
/>
|
<CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||||
</div>
|
<CardTitle class="text-sm font-medium">{{ t('dashboard.inProgress') }}</CardTitle>
|
||||||
|
<Activity class="size-4 text-muted-foreground" />
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div class="text-2xl font-bold">{{ workItems.inProgressHUs }}</div>
|
||||||
|
<p class="text-xs text-muted-foreground">{{ t('dashboard.activeHus') }}</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card id="dashboard-stats-qa" class="bg-gradient-to-t from-primary/5 to-card shadow-xs dark:bg-card">
|
<Card id="dashboard-stats-qa" class="bg-gradient-to-t from-primary/5 to-card shadow-xs dark:bg-card">
|
||||||
<CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
|
<CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||||
<CardTitle class="text-xs font-medium">QA</CardTitle>
|
<CardTitle class="text-sm font-medium">QA</CardTitle>
|
||||||
<CheckCircle2 class="size-3.5 text-muted-foreground" />
|
<CheckCircle2 class="size-4 text-muted-foreground" />
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div class="text-xl font-bold">{{ qaMetrics.total }}</div>
|
<div class="text-2xl font-bold">{{ qaMetrics.total }}</div>
|
||||||
<p class="text-[10px] text-muted-foreground">Planes QA · {{ qaMetrics.totalCases }} casos</p>
|
<p class="text-xs text-muted-foreground">Planes QA · {{ qaMetrics.totalCases }} casos</p>
|
||||||
<div class="flex gap-2 mt-1 text-[10px]">
|
<div class="flex gap-2 mt-1.5 text-[10px]">
|
||||||
<span class="text-green-600 dark:text-green-400">{{ qaMetrics.auto }} auto</span>
|
<span class="text-green-600 dark:text-green-400">{{ qaMetrics.auto }} auto</span>
|
||||||
<span class="text-amber-600 dark:text-amber-400">{{ qaMetrics.parcial }} parcial</span>
|
<span class="text-amber-600 dark:text-amber-400">{{ qaMetrics.parcial }} parcial</span>
|
||||||
<span class="text-red-600 dark:text-red-400">{{ qaMetrics.manual }} manual</span>
|
<span class="text-red-600 dark:text-red-400">{{ qaMetrics.manual }} manual</span>
|
||||||
@@ -711,22 +662,12 @@ const statusLabel = (status: unknown) => {
|
|||||||
|
|
||||||
<Card id="dashboard-stats-sessions" class="bg-gradient-to-t from-primary/5 to-card shadow-xs dark:bg-card">
|
<Card id="dashboard-stats-sessions" class="bg-gradient-to-t from-primary/5 to-card shadow-xs dark:bg-card">
|
||||||
<CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
|
<CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||||
<CardTitle class="text-xs font-medium">{{ t('dashboard.sessions') }}</CardTitle>
|
<CardTitle class="text-sm font-medium">{{ t('dashboard.sessions') }}</CardTitle>
|
||||||
<Clock class="size-3.5 text-muted-foreground" />
|
<Clock class="size-4 text-muted-foreground" />
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div class="text-xl font-bold">{{ workItems.totalSessions }}</div>
|
<div class="text-2xl font-bold">{{ workItems.totalSessions }}</div>
|
||||||
<p class="text-[10px] text-muted-foreground">{{ t('dashboard.sessionsSubtitle') }}</p>
|
<p class="text-xs text-muted-foreground">{{ t('dashboard.sessionsSubtitle') }}</p>
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card id="dashboard-stats-sc" class="bg-gradient-to-t from-primary/5 to-card shadow-xs dark:bg-card">
|
|
||||||
<CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
||||||
<CardTitle class="text-xs font-medium">Curva S</CardTitle>
|
|
||||||
<TrendingUp class="size-3.5 text-muted-foreground" />
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent class="p-2">
|
|
||||||
<SCurveChart v-if="project" :project-id="project.id" :compact="true" />
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user