QA plans al sugerir HU + cancel analisis + datepicker shadcn

- project-analyzer: saveAsDrafts genera QA plan por cada HU sugerida
- DashboardView: cancelAnalysis con AbortController + mensaje limpio
- HuDrafts: DatePicker con Calendar + Popover (shadcn-vue)
- HuDrafts: formulario dinámico segun tipo (Epic vs HU/Feature/etc)
- components/ui: Popover + Calendar creados
- qa_plans: tabla separada (cubre drafts + user_stories existentes)
This commit is contained in:
2026-05-28 23:25:00 -05:00
parent dd9f76be6f
commit 8667cddc46
10 changed files with 204 additions and 42 deletions
+35 -20
View File
@@ -108,10 +108,6 @@ async function pushDraft(d: HuDraftRecord) {
})
if (res.ok) {
d.syncStatus = 'pushed'; await dbSaveDraft(d)
// Auto-generar QA plan tras push exitoso de épica
generateAndSavePlan(d.projectId, d.id, d.title, d.description || '', '').catch(e =>
console.error(`[Alpha] QA plan auto-gen failed for epic ${d.title}:`, e)
)
} else {
d.syncStatus = 'draft'; await dbSaveDraft(d)
}
@@ -136,10 +132,6 @@ async function pushDraft(d: HuDraftRecord) {
d.kappaId = created.id || undefined
d.syncStatus = 'pushed'
await dbSaveDraft(d)
// Auto-generar QA plan tras push exitoso
generateAndSavePlan(d.projectId, d.id, d.title, d.description || '', d.acceptanceCriteria).catch(e =>
console.error(`[Alpha] QA plan auto-gen failed for ${d.title}:`, e)
)
} else {
d.syncStatus = 'draft'; await dbSaveDraft(d)
}
@@ -168,17 +160,26 @@ async function discardDraft(id: string) {
// ─── Project analysis ────────────────────────────────────
const analyzing = ref(false)
const analysisAbort = ref<AbortController | null>(null)
const analysisResult = ref<{ saved: number; skipped: number } | null>(null)
const analysisSummary = ref('')
function cancelAnalysis() {
analysisAbort.value?.abort()
analyzing.value = false
analysisAbort.value = null
analysisSummary.value = 'Análisis cancelado'
}
async function runAnalysis() {
if (!project.value) return
analyzing.value = true
analysisAbort.value = new AbortController()
analysisResult.value = null
analysisSummary.value = ''
try {
const result = await analyzeProject(project.value.id, project.value.name || '', workItems.userStories)
const result = await analyzeProject(project.value.id, project.value.name || '', workItems.userStories, analysisAbort.value?.signal)
analysisSummary.value = result.summary
if (result.hus.length > 0) {
@@ -188,8 +189,12 @@ async function runAnalysis() {
analysisResult.value = { saved: 0, skipped: 0 }
}
} catch (e: any) {
console.error('[Alpha] Analysis error:', e)
analysisSummary.value = `Error: ${e.message}`
if (e.name === 'AbortError' || e.message?.includes('aborted')) {
analysisSummary.value = 'Análisis cancelado'
} else {
console.error('[Alpha] Analysis error:', e)
analysisSummary.value = `Error: ${e.message}`
}
analysisResult.value = { saved: 0, skipped: 0 }
} finally {
analyzing.value = false
@@ -313,15 +318,25 @@ const statusLabel = (status: unknown) => {
<Sparkles class="size-4" />
Análisis completo del proyecto
</CardTitle>
<Button
size="sm"
:disabled="analyzing"
@click="runAnalysis()"
>
<Loader2 v-if="analyzing" class="size-4 mr-1 animate-spin" />
<Sparkles v-else class="size-4 mr-1" />
{{ analyzing ? 'Analizando...' : 'Generar HUs faltantes' }}
</Button>
<div class="flex gap-2">
<Button
v-if="analyzing"
size="sm"
variant="outline"
@click="cancelAnalysis()"
>
Cancelar
</Button>
<Button
size="sm"
:disabled="analyzing"
@click="runAnalysis()"
>
<Loader2 v-if="analyzing" class="size-4 mr-1 animate-spin" />
<Sparkles v-else class="size-4 mr-1" />
{{ analyzing ? 'Analizando...' : 'Generar HUs faltantes' }}
</Button>
</div>
</CardHeader>
<CardContent v-if="analysisResult" class="space-y-2 text-sm">
<p class="text-muted-foreground">{{ analysisSummary }}</p>