ProjectListView + DashboardView: mostrar ID del proyecto (#305) junto al nombre

- pushDraft: épica usa JSON con client_taker + status=false
- ToastNotification: sistema de notificaciones toast global
- useToast: composable singleton para mostrar/descartar toasts
This commit is contained in:
2026-05-29 12:01:11 -05:00
parent 19c6fb3153
commit b45caee583
6 changed files with 125 additions and 39 deletions
+50 -39
View File
@@ -11,6 +11,7 @@ import { Button } from '@/components/ui/button'
import HuDrafts from '@/components/HuDrafts.vue'
import AiProjectChat from '@/components/AiProjectChat.vue'
import { analyzeProject, saveAsDrafts } from '@/services/project-analyzer'
import { useToast } from '@/composables/useToast'
import { getDrafts, deleteDraft, type HuDraftRecord } from '@/services/hu-drafts-db'
import { kappa } from '@/services/kappa-api'
import { generateAndSavePlan, getQAPlans, type HUQAPlan } from '@/services/qa-analyzer'
@@ -36,7 +37,7 @@ const { t } = useI18n()
const projects = useProjectsStore()
const workItems = useWorkItemsStore()
const usersStore = useUsersStore()
const { show: showToast } = useToast()
const project = computed(() => projects.selected)
// ─── Team members for this project ────────────────────
@@ -267,57 +268,64 @@ async function pushDraft(d: HuDraftRecord) {
const token = localStorage.getItem('kappa_token')
const headers = { 'Content-Type': 'application/json', ...(token ? { 'Authorization': `Bearer ${token}` } : {}) }
let endpoint: string, body: string
if (d.type === 'E') {
// Push épica: buscar HUs vinculadas ya enviadas
// Push épica
const meta = JSON.parse(d.metadata || '{}')
const linkedHuIds: number[] = []
for (const huTitle of meta.linkedHuTitles || []) {
const pushedHu = drafts.value.find(x => x.type !== 'E' && (x.title.toLowerCase().trim() === huTitle.toLowerCase().trim()) && x.kappaId)
if (pushedHu?.kappaId) linkedHuIds.push(pushedHu.kappaId)
}
const res = await fetch('/api/epicdevelopment/create/', {
method: 'POST', headers,
body: JSON.stringify({
initiative: String(d.projectId), name: d.title,
description: d.description ? `<p>${d.description.replace(/\n/g, '</p><p>')}</p>` : '',
client_taker: null, hu: linkedHuIds,
stimated_start_date: meta.estimatedStart || null,
stimated_end_date: meta.estimatedEnd || null,
}),
endpoint = '/api/epicdevelopment/create/'
body = JSON.stringify({
initiative: String(d.projectId),
name: d.title,
description: d.description ? `<p>${d.description.replace(/\n/g, '</p><p>')}</p>` : '',
stimated_start_date: meta.estimatedStart || null,
stimated_end_date: meta.estimatedEnd || null,
client_taker: Number(meta.clientTaker) || null,
hu: linkedHuIds,
status: false,
})
if (res.ok) {
d.syncStatus = 'pushed'; await dbSaveDraft(d)
} else {
d.syncStatus = 'draft'; await dbSaveDraft(d)
}
} else {
// Push HU individual
const res = await fetch('/api/userstorys/create/', {
method: 'POST', headers,
body: JSON.stringify({
initiative: String(d.projectId), title: d.title,
description: d.description ? `<p>${d.description.replace(/\n/g, '</p><p>')}</p>` : '',
criterios_aceptacion: d.acceptanceCriteria ? `<p>${d.acceptanceCriteria.replace(/\n/g, '</p><p>')}</p>` : '',
story_points: '',
priority: d.priority === 'Alta' ? '1' : d.priority === 'Baja' ? '3' : '2',
sprint: '', asignado_a: [], client_taker: null,
characterization_hu: '', has_impairment: false,
epic_development: null, feature: '',
initial_date: null, end_date: null,
}),
// Push HU
endpoint = '/api/userstorys/create/'
body = JSON.stringify({
initiative: String(d.projectId), title: d.title,
description: d.description ? `<p>${d.description.replace(/\n/g, '</p><p>')}</p>` : '',
criterios_aceptacion: d.acceptanceCriteria ? `<p>${d.acceptanceCriteria.replace(/\n/g, '</p><p>')}</p>` : '',
story_points: String(d.story_points ?? ''),
priority: d.priority === 'Alta' ? '1' : d.priority === 'Baja' ? '3' : '2',
sprint: '', asignado_a: [], client_taker: null,
characterization_hu: '', has_impairment: false,
epic_development: null, feature: '',
initial_date: null, end_date: null,
})
if (res.ok) {
}
const res = await fetch(endpoint, { method: 'POST', headers, body })
if (res.ok) {
if (d.type !== 'E') {
const created = await res.json()
d.kappaId = created.id || undefined
d.syncStatus = 'pushed'
await dbSaveDraft(d)
} else {
d.syncStatus = 'draft'; await dbSaveDraft(d)
}
d.syncStatus = 'pushed'
await dbSaveDraft(d)
showToast('success', d.type === 'E' ? 'Épica creada en KAPPA' : 'HU creada en KAPPA', d.title.slice(0, 100))
} else {
const errorText = await res.text().catch(() => 'Error desconocido')
console.error(`[Alpha] Error push a KAPPA (${endpoint}): ${res.status}${errorText}`)
showToast('error', 'Error al crear en KAPPA', errorText.slice(0, 300))
d.syncStatus = 'draft'
await dbSaveDraft(d)
}
} catch {
d.syncStatus = 'draft'; await dbSaveDraft(d)
} catch (e: any) {
console.error('[Alpha] Error en pushDraft:', e)
showToast('error', 'Error de red', e.message)
d.syncStatus = 'draft'
await dbSaveDraft(d)
} finally {
pushingDraftId.value = null
await loadDrafts()
@@ -420,7 +428,10 @@ const statusLabel = (status: unknown) => {
<template>
<div v-if="project" class="@container/main flex flex-1 flex-col gap-4 px-4 lg:px-6">
<div class="flex flex-col gap-1">
<h1 class="text-2xl font-bold tracking-tight">{{ project.name }}</h1>
<h1 class="text-2xl font-bold tracking-tight">
<span class="font-mono text-sm text-muted-foreground mr-2">#{{ project.id }}</span>
{{ project.name }}
</h1>
<div class="flex items-center gap-2">
<Badge v-if="project.key" variant="outline" class="text-xs">{{ project.key }}</Badge>
</div>