diff --git a/src/components/ToastNotification.vue b/src/components/ToastNotification.vue new file mode 100644 index 0000000..fe9bf96 --- /dev/null +++ b/src/components/ToastNotification.vue @@ -0,0 +1,42 @@ + + + + + diff --git a/src/composables/useToast.ts b/src/composables/useToast.ts new file mode 100644 index 0000000..fafe9c2 --- /dev/null +++ b/src/composables/useToast.ts @@ -0,0 +1,29 @@ +import { ref } from 'vue' + +export interface Toast { + id: string + type: 'success' | 'error' | 'info' + title: string + message?: string +} + +const toasts = ref([]) + +let counter = 0 + +export function useToast() { + function show(type: Toast['type'], title: string, message?: string, duration = 5000) { + const id = `toast-${++counter}` + toasts.value.push({ id, type, title, message }) + if (duration > 0) { + setTimeout(() => dismiss(id), duration) + } + } + + function dismiss(id: string) { + const idx = toasts.value.findIndex(t => t.id === id) + if (idx !== -1) toasts.value.splice(idx, 1) + } + + return { toasts, show, dismiss } +} diff --git a/src/services/db.ts b/src/services/db.ts index 5a64659..c1b4610 100644 --- a/src/services/db.ts +++ b/src/services/db.ts @@ -52,6 +52,7 @@ export interface HuDraftRecord { description: string acceptanceCriteria: string priority: string + story_points?: number type: string // 'U' = HU, 'E' = Epic, 'F' = Feature, 'T' = Task, 'B' = Bug metadata: string // JSON opcional: { linkedHuTitles?: string[], estimatedStart?: string, estimatedEnd?: string } sourceSessionId?: number diff --git a/src/views/DashboardView.vue b/src/views/DashboardView.vue index 121242c..7fa399f 100644 --- a/src/views/DashboardView.vue +++ b/src/views/DashboardView.vue @@ -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 ? `

${d.description.replace(/\n/g, '

')}

` : '', - 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 ? `

${d.description.replace(/\n/g, '

')}

` : '', + 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 ? `

${d.description.replace(/\n/g, '

')}

` : '', - criterios_aceptacion: d.acceptanceCriteria ? `

${d.acceptanceCriteria.replace(/\n/g, '

')}

` : '', - 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 ? `

${d.description.replace(/\n/g, '

')}

` : '', + criterios_aceptacion: d.acceptanceCriteria ? `

${d.acceptanceCriteria.replace(/\n/g, '

')}

` : '', + 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) => { diff --git a/src/views/ProjectListView.vue b/src/views/ProjectListView.vue index f123e2d..b749061 100644 --- a/src/views/ProjectListView.vue +++ b/src/views/ProjectListView.vue @@ -231,6 +231,7 @@ onMounted(async () => {
+ #{{ p.id }} {{ p.initiative_name || p.name || t('projects.unnamedFallback', { id: p.id }) }}