fix: Simplificar UI para usar Nuxt UI v3 basico (sin Pro)
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 3m16s
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 3m16s
This commit is contained in:
@@ -1,23 +1,26 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex items-center gap-2 px-3 py-1 rounded-full bg-[var(--wa-surface)] border border-[var(--wa-border)]">
|
<div class="flex items-center gap-2 px-3 py-1 rounded-full bg-gray-700 border border-gray-600">
|
||||||
<span
|
<span
|
||||||
class="w-2 h-2 rounded-full"
|
class="w-2 h-2 rounded-full"
|
||||||
:class="statusClass"
|
:class="statusClass"
|
||||||
/>
|
/>
|
||||||
<span class="text-sm text-[var(--wa-text-muted)]">
|
<span class="text-sm text-gray-300">
|
||||||
{{ connectedCount }} / {{ totalCount }} conectadas
|
{{ connectedCount }} / {{ totalCount }} conectadas
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
// TODO: Conectar con useInstances cuando esté implementado
|
const { instances } = useInstances()
|
||||||
const connectedCount = ref(0)
|
|
||||||
const totalCount = ref(0)
|
const connectedCount = computed(() =>
|
||||||
|
instances.value.filter(i => i.status === 'connected').length
|
||||||
|
)
|
||||||
|
const totalCount = computed(() => instances.value.length)
|
||||||
|
|
||||||
const statusClass = computed(() => {
|
const statusClass = computed(() => {
|
||||||
if (totalCount.value === 0) return 'bg-gray-500'
|
if (totalCount.value === 0) return 'bg-gray-500'
|
||||||
if (connectedCount.value === totalCount.value) return 'bg-[var(--wa-green-light)]'
|
if (connectedCount.value === totalCount.value) return 'bg-green-500'
|
||||||
if (connectedCount.value === 0) return 'bg-red-500'
|
if (connectedCount.value === 0) return 'bg-red-500'
|
||||||
return 'bg-yellow-500'
|
return 'bg-yellow-500'
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,36 +1,62 @@
|
|||||||
<template>
|
<template>
|
||||||
<UDropdownMenu v-if="user">
|
<div v-if="user" class="relative">
|
||||||
<UButton
|
<UButton
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
class="rounded-full p-0"
|
color="neutral"
|
||||||
|
class="rounded-full p-1"
|
||||||
|
@click="menuOpen = !menuOpen"
|
||||||
>
|
>
|
||||||
<UAvatar
|
<div class="w-8 h-8 rounded-full bg-green-600 flex items-center justify-center text-white font-medium">
|
||||||
:src="user.avatar"
|
{{ initials }}
|
||||||
:alt="user.name || user.username"
|
</div>
|
||||||
size="sm"
|
|
||||||
/>
|
|
||||||
</UButton>
|
</UButton>
|
||||||
|
|
||||||
<template #content>
|
<!-- Dropdown menu -->
|
||||||
<div class="px-3 py-2 border-b border-[var(--wa-border)]">
|
<Transition
|
||||||
<p class="font-medium text-[var(--wa-text)]">{{ user.name || user.username }}</p>
|
enter-active-class="transition ease-out duration-100"
|
||||||
<p class="text-sm text-[var(--wa-text-muted)]">{{ user.email }}</p>
|
enter-from-class="transform opacity-0 scale-95"
|
||||||
|
enter-to-class="transform opacity-100 scale-100"
|
||||||
|
leave-active-class="transition ease-in duration-75"
|
||||||
|
leave-from-class="transform opacity-100 scale-100"
|
||||||
|
leave-to-class="transform opacity-0 scale-95"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="menuOpen"
|
||||||
|
class="absolute right-0 mt-2 w-48 bg-gray-800 rounded-lg shadow-lg border border-gray-700 py-1 z-50"
|
||||||
|
>
|
||||||
|
<div class="px-4 py-2 border-b border-gray-700">
|
||||||
|
<p class="font-medium text-white">{{ user.name || user.username }}</p>
|
||||||
|
<p class="text-sm text-gray-400">{{ user.email }}</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="w-full px-4 py-2 text-left text-gray-300 hover:bg-gray-700 flex items-center gap-2"
|
||||||
|
@click="logout"
|
||||||
|
>
|
||||||
|
<UIcon name="i-lucide-log-out" class="w-4 h-4" />
|
||||||
|
Cerrar Sesion
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</Transition>
|
||||||
<UDropdownMenuItem
|
</div>
|
||||||
icon="i-lucide-user"
|
|
||||||
label="Mi Perfil"
|
|
||||||
@click="goToProfile"
|
|
||||||
/>
|
|
||||||
<UDropdownMenuItem
|
|
||||||
icon="i-lucide-log-out"
|
|
||||||
label="Cerrar Sesion"
|
|
||||||
@click="logout"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</UDropdownMenu>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const { user, logout, goToProfile } = useAuthentik()
|
const { user, logout } = useAuthentik()
|
||||||
|
const menuOpen = ref(false)
|
||||||
|
|
||||||
|
const initials = computed(() => {
|
||||||
|
if (!user.value) return '?'
|
||||||
|
const name = user.value.name || user.value.username || ''
|
||||||
|
return name.substring(0, 2).toUpperCase()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Close menu when clicking outside
|
||||||
|
onMounted(() => {
|
||||||
|
document.addEventListener('click', (e) => {
|
||||||
|
const target = e.target as HTMLElement
|
||||||
|
if (!target.closest('.relative')) {
|
||||||
|
menuOpen.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,39 +1,93 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="whatsapp-shell min-h-screen text-[var(--wa-text)]">
|
<div class="min-h-screen bg-gray-900 text-gray-100">
|
||||||
<UDashboardGroup storage-key="whatsapp-dashboard" class="h-full">
|
<!-- Sidebar -->
|
||||||
<AppSidebar />
|
<aside
|
||||||
|
:class="[
|
||||||
|
'fixed inset-y-0 left-0 z-50 w-64 bg-gray-800 border-r border-gray-700 transform transition-transform duration-200 ease-in-out lg:translate-x-0',
|
||||||
|
sidebarOpen ? 'translate-x-0' : '-translate-x-full'
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<div class="flex items-center justify-between h-16 px-4 border-b border-gray-700">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<UIcon name="i-lucide-message-circle" class="w-6 h-6 text-green-500" />
|
||||||
|
<span class="text-lg font-semibold">WhatsApp Nucleo</span>
|
||||||
|
</div>
|
||||||
|
<UButton
|
||||||
|
icon="i-lucide-x"
|
||||||
|
variant="ghost"
|
||||||
|
color="neutral"
|
||||||
|
class="lg:hidden"
|
||||||
|
@click="sidebarOpen = false"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<UDashboardPanel class="bg-transparent">
|
<nav class="p-4 space-y-2">
|
||||||
<template #header>
|
<NuxtLink
|
||||||
<div class="flex flex-col gap-4 px-4 py-4 lg:px-6">
|
v-for="item in menuItems"
|
||||||
<UDashboardNavbar :title="pageTitle" :icon="pageIcon" toggle-side="left">
|
:key="item.to"
|
||||||
<template #leading>
|
:to="item.to"
|
||||||
<UDashboardSidebarCollapse variant="subtle" />
|
class="flex items-center gap-3 px-3 py-2 rounded-lg transition-colors"
|
||||||
</template>
|
:class="[
|
||||||
<template #toggle>
|
route.path === item.to
|
||||||
<UDashboardSidebarToggle variant="subtle" />
|
? 'bg-green-600 text-white'
|
||||||
</template>
|
: 'text-gray-300 hover:bg-gray-700'
|
||||||
<template #trailing>
|
]"
|
||||||
<ConnectionStatus />
|
>
|
||||||
<UserMenu />
|
<UIcon :name="item.icon" class="w-5 h-5" />
|
||||||
</template>
|
<span>{{ item.label }}</span>
|
||||||
</UDashboardNavbar>
|
</NuxtLink>
|
||||||
</div>
|
</nav>
|
||||||
</template>
|
</aside>
|
||||||
|
|
||||||
<template #body>
|
<!-- Main content -->
|
||||||
<div class="px-4 pb-10 lg:px-8">
|
<div class="lg:pl-64">
|
||||||
<slot />
|
<!-- Header -->
|
||||||
</div>
|
<header class="sticky top-0 z-40 flex items-center justify-between h-16 px-4 bg-gray-800 border-b border-gray-700">
|
||||||
</template>
|
<div class="flex items-center gap-4">
|
||||||
</UDashboardPanel>
|
<UButton
|
||||||
</UDashboardGroup>
|
icon="i-lucide-menu"
|
||||||
|
variant="ghost"
|
||||||
|
color="neutral"
|
||||||
|
class="lg:hidden"
|
||||||
|
@click="sidebarOpen = true"
|
||||||
|
/>
|
||||||
|
<h1 class="text-lg font-semibold">{{ pageTitle }}</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<ConnectionStatus />
|
||||||
|
<UserMenu />
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- Page content -->
|
||||||
|
<main class="p-4 lg:p-6">
|
||||||
|
<slot />
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Mobile overlay -->
|
||||||
|
<div
|
||||||
|
v-if="sidebarOpen"
|
||||||
|
class="fixed inset-0 z-40 bg-black/50 lg:hidden"
|
||||||
|
@click="sidebarOpen = false"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
const sidebarOpen = ref(false)
|
||||||
|
|
||||||
const pageTitle = computed(() => (route.meta.title as string) || 'WhatsApp Nucleo')
|
const pageTitle = computed(() => (route.meta.title as string) || 'Dashboard')
|
||||||
const pageIcon = computed(() => (route.meta.icon as string) || 'i-lucide-message-circle')
|
|
||||||
|
const menuItems = [
|
||||||
|
{ to: '/', label: 'Instancias', icon: 'i-lucide-smartphone' },
|
||||||
|
{ to: '/webhooks', label: 'Webhooks', icon: 'i-lucide-webhook' },
|
||||||
|
]
|
||||||
|
|
||||||
|
// Close sidebar on route change (mobile)
|
||||||
|
watch(() => route.path, () => {
|
||||||
|
sidebarOpen.value = false
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -2,94 +2,174 @@
|
|||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<!-- Stats Cards -->
|
<!-- Stats Cards -->
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||||
<MetricCard
|
<div class="bg-gray-800 rounded-lg p-4 border border-gray-700">
|
||||||
title="Instancias Activas"
|
<div class="flex items-center gap-3">
|
||||||
:value="stats.connectedInstances"
|
<div class="p-2 bg-green-900/50 rounded-lg">
|
||||||
:total="stats.totalInstances"
|
<UIcon name="i-lucide-smartphone" class="w-5 h-5 text-green-400" />
|
||||||
icon="i-lucide-smartphone"
|
</div>
|
||||||
color="green"
|
<div>
|
||||||
/>
|
<p class="text-sm text-gray-400">Instancias</p>
|
||||||
<MetricCard
|
<p class="text-xl font-semibold text-white">{{ connectedCount }} / {{ instances.length }}</p>
|
||||||
title="Mensajes Hoy"
|
|
||||||
:value="stats.messagesToday"
|
|
||||||
icon="i-lucide-message-square"
|
|
||||||
color="blue"
|
|
||||||
/>
|
|
||||||
<MetricCard
|
|
||||||
title="Webhooks Activos"
|
|
||||||
:value="stats.activeWebhooks"
|
|
||||||
icon="i-lucide-webhook"
|
|
||||||
color="purple"
|
|
||||||
/>
|
|
||||||
<MetricCard
|
|
||||||
title="Chats Activos"
|
|
||||||
:value="stats.activeChats"
|
|
||||||
icon="i-lucide-users"
|
|
||||||
color="amber"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Quick Actions -->
|
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
||||||
<!-- Instances Overview -->
|
|
||||||
<div class="instance-card p-6">
|
|
||||||
<div class="flex items-center justify-between mb-4">
|
|
||||||
<h3 class="text-lg font-semibold text-[var(--wa-text)]">Instancias</h3>
|
|
||||||
<UButton
|
|
||||||
to="/instances"
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
trailing-icon="i-lucide-arrow-right"
|
|
||||||
>
|
|
||||||
Ver todas
|
|
||||||
</UButton>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="instances.length === 0" class="text-center py-8">
|
|
||||||
<UIcon name="i-lucide-smartphone" class="w-12 h-12 text-[var(--wa-text-muted)] mx-auto mb-3" />
|
|
||||||
<p class="text-[var(--wa-text-muted)]">No hay instancias configuradas</p>
|
|
||||||
<UButton
|
|
||||||
to="/instances"
|
|
||||||
variant="soft"
|
|
||||||
class="mt-4"
|
|
||||||
>
|
|
||||||
Crear primera instancia
|
|
||||||
</UButton>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else class="space-y-3">
|
|
||||||
<div
|
|
||||||
v-for="instance in instances.slice(0, 3)"
|
|
||||||
:key="instance.id"
|
|
||||||
class="flex items-center justify-between p-3 rounded-lg bg-[var(--wa-bg-light)]"
|
|
||||||
>
|
|
||||||
<div class="flex items-center gap-3">
|
|
||||||
<span
|
|
||||||
class="w-3 h-3 rounded-full"
|
|
||||||
:class="instance.status === 'connected' ? 'bg-[var(--wa-green-light)]' : 'bg-red-500'"
|
|
||||||
/>
|
|
||||||
<div>
|
|
||||||
<p class="font-medium text-[var(--wa-text)]">{{ instance.name }}</p>
|
|
||||||
<p class="text-sm text-[var(--wa-text-muted)]">{{ instance.phoneNumber || 'Sin conectar' }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<StatusBadge :status="instance.status" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Recent Activity -->
|
<div class="bg-gray-800 rounded-lg p-4 border border-gray-700">
|
||||||
<div class="instance-card p-6">
|
<div class="flex items-center gap-3">
|
||||||
<div class="flex items-center justify-between mb-4">
|
<div class="p-2 bg-blue-900/50 rounded-lg">
|
||||||
<h3 class="text-lg font-semibold text-[var(--wa-text)]">Actividad Reciente</h3>
|
<UIcon name="i-lucide-message-square" class="w-5 h-5 text-blue-400" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="text-sm text-gray-400">Mensajes Hoy</p>
|
||||||
|
<p class="text-xl font-semibold text-white">0</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="text-center py-8">
|
<div class="bg-gray-800 rounded-lg p-4 border border-gray-700">
|
||||||
<UIcon name="i-lucide-activity" class="w-12 h-12 text-[var(--wa-text-muted)] mx-auto mb-3" />
|
<div class="flex items-center gap-3">
|
||||||
<p class="text-[var(--wa-text-muted)]">La actividad aparecera aqui</p>
|
<div class="p-2 bg-purple-900/50 rounded-lg">
|
||||||
|
<UIcon name="i-lucide-webhook" class="w-5 h-5 text-purple-400" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="text-sm text-gray-400">Webhooks</p>
|
||||||
|
<p class="text-xl font-semibold text-white">0</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-gray-800 rounded-lg p-4 border border-gray-700">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<div class="p-2 bg-amber-900/50 rounded-lg">
|
||||||
|
<UIcon name="i-lucide-users" class="w-5 h-5 text-amber-400" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="text-sm text-gray-400">Chats Activos</p>
|
||||||
|
<p class="text-xl font-semibold text-white">0</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Instances Section -->
|
||||||
|
<div class="bg-gray-800 rounded-lg border border-gray-700">
|
||||||
|
<div class="flex items-center justify-between p-4 border-b border-gray-700">
|
||||||
|
<h2 class="text-lg font-semibold text-white">Instancias de WhatsApp</h2>
|
||||||
|
<UButton
|
||||||
|
icon="i-lucide-plus"
|
||||||
|
color="primary"
|
||||||
|
@click="showCreateModal = true"
|
||||||
|
>
|
||||||
|
Nueva Instancia
|
||||||
|
</UButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="loading" class="p-8 text-center">
|
||||||
|
<UIcon name="i-lucide-loader-2" class="w-8 h-8 text-gray-400 animate-spin mx-auto" />
|
||||||
|
<p class="text-gray-400 mt-2">Cargando instancias...</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="instances.length === 0" class="p-8 text-center">
|
||||||
|
<UIcon name="i-lucide-smartphone" class="w-12 h-12 text-gray-500 mx-auto mb-3" />
|
||||||
|
<p class="text-gray-400 mb-4">No hay instancias configuradas</p>
|
||||||
|
<UButton color="primary" @click="showCreateModal = true">
|
||||||
|
Crear primera instancia
|
||||||
|
</UButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else class="divide-y divide-gray-700">
|
||||||
|
<div
|
||||||
|
v-for="instance in instances"
|
||||||
|
:key="instance.id"
|
||||||
|
class="p-4 flex items-center justify-between hover:bg-gray-700/50 transition-colors"
|
||||||
|
>
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<span
|
||||||
|
class="w-3 h-3 rounded-full"
|
||||||
|
:class="{
|
||||||
|
'bg-green-500': instance.status === 'connected',
|
||||||
|
'bg-yellow-500': instance.status === 'connecting' || instance.status === 'qr_ready',
|
||||||
|
'bg-red-500': instance.status === 'disconnected'
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<p class="font-medium text-white">{{ instance.name }}</p>
|
||||||
|
<p class="text-sm text-gray-400">{{ instance.phone_number || 'Sin conectar' }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span
|
||||||
|
class="px-2 py-1 text-xs rounded-full"
|
||||||
|
:class="{
|
||||||
|
'bg-green-900/50 text-green-400': instance.status === 'connected',
|
||||||
|
'bg-yellow-900/50 text-yellow-400': instance.status === 'qr_ready',
|
||||||
|
'bg-gray-700 text-gray-400': instance.status === 'disconnected'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ statusText(instance.status) }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<UButton
|
||||||
|
v-if="instance.status === 'disconnected'"
|
||||||
|
size="sm"
|
||||||
|
color="primary"
|
||||||
|
variant="soft"
|
||||||
|
@click="connectInstance(instance.id)"
|
||||||
|
>
|
||||||
|
Conectar
|
||||||
|
</UButton>
|
||||||
|
|
||||||
|
<UButton
|
||||||
|
v-if="instance.status === 'qr_ready'"
|
||||||
|
size="sm"
|
||||||
|
color="primary"
|
||||||
|
@click="showQR(instance)"
|
||||||
|
>
|
||||||
|
Ver QR
|
||||||
|
</UButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Create Instance Modal -->
|
||||||
|
<UModal v-model:open="showCreateModal">
|
||||||
|
<template #content>
|
||||||
|
<div class="p-6">
|
||||||
|
<h3 class="text-lg font-semibold text-white mb-4">Nueva Instancia</h3>
|
||||||
|
<form @submit.prevent="createInstance">
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-300 mb-1">Nombre</label>
|
||||||
|
<UInput v-model="newInstanceName" placeholder="Ej: Ventas, Soporte..." />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-end gap-2 mt-6">
|
||||||
|
<UButton variant="ghost" color="neutral" @click="showCreateModal = false">
|
||||||
|
Cancelar
|
||||||
|
</UButton>
|
||||||
|
<UButton type="submit" color="primary" :loading="creating">
|
||||||
|
Crear
|
||||||
|
</UButton>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</UModal>
|
||||||
|
|
||||||
|
<!-- QR Code Modal -->
|
||||||
|
<UModal v-model:open="showQRModal">
|
||||||
|
<template #content>
|
||||||
|
<div class="p-6 text-center">
|
||||||
|
<h3 class="text-lg font-semibold text-white mb-4">Escanea el QR</h3>
|
||||||
|
<div v-if="currentQR" class="bg-white p-4 rounded-lg inline-block">
|
||||||
|
<img :src="currentQR" alt="QR Code" class="w-64 h-64" />
|
||||||
|
</div>
|
||||||
|
<p class="text-gray-400 mt-4">Abre WhatsApp en tu telefono y escanea este codigo</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</UModal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -97,17 +177,54 @@
|
|||||||
definePageMeta({
|
definePageMeta({
|
||||||
layout: 'dashboard',
|
layout: 'dashboard',
|
||||||
title: 'Dashboard',
|
title: 'Dashboard',
|
||||||
icon: 'i-lucide-layout-dashboard'
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO: Conectar con datos reales
|
const { instances, loading, fetchInstances, createInstance: apiCreateInstance, connectInstance: apiConnect } = useInstances()
|
||||||
const stats = ref({
|
|
||||||
totalInstances: 0,
|
|
||||||
connectedInstances: 0,
|
|
||||||
messagesToday: 0,
|
|
||||||
activeWebhooks: 0,
|
|
||||||
activeChats: 0
|
|
||||||
})
|
|
||||||
|
|
||||||
const instances = ref<any[]>([])
|
const showCreateModal = ref(false)
|
||||||
|
const showQRModal = ref(false)
|
||||||
|
const newInstanceName = ref('')
|
||||||
|
const creating = ref(false)
|
||||||
|
const currentQR = ref<string | null>(null)
|
||||||
|
|
||||||
|
const connectedCount = computed(() =>
|
||||||
|
instances.value.filter(i => i.status === 'connected').length
|
||||||
|
)
|
||||||
|
|
||||||
|
const statusText = (status: string) => {
|
||||||
|
const map: Record<string, string> = {
|
||||||
|
connected: 'Conectado',
|
||||||
|
connecting: 'Conectando...',
|
||||||
|
qr_ready: 'Esperando QR',
|
||||||
|
disconnected: 'Desconectado',
|
||||||
|
pairing: 'Emparejando'
|
||||||
|
}
|
||||||
|
return map[status] || status
|
||||||
|
}
|
||||||
|
|
||||||
|
const createInstance = async () => {
|
||||||
|
if (!newInstanceName.value.trim()) return
|
||||||
|
creating.value = true
|
||||||
|
try {
|
||||||
|
await apiCreateInstance(newInstanceName.value)
|
||||||
|
showCreateModal.value = false
|
||||||
|
newInstanceName.value = ''
|
||||||
|
} finally {
|
||||||
|
creating.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const connectInstance = async (id: string) => {
|
||||||
|
await apiConnect(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
const showQR = (instance: any) => {
|
||||||
|
currentQR.value = instance.qr_code
|
||||||
|
showQRModal.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch instances on mount
|
||||||
|
onMounted(() => {
|
||||||
|
fetchInstances()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user