All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 51s
247 lines
8.0 KiB
Vue
247 lines
8.0 KiB
Vue
<template>
|
|
<div class="space-y-6">
|
|
<!-- Stats Cards -->
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
|
<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-green-900/50 rounded-lg">
|
|
<UIcon name="i-lucide-smartphone" class="w-5 h-5 text-green-400" />
|
|
</div>
|
|
<div>
|
|
<p class="text-sm text-gray-400">Instancias</p>
|
|
<p class="text-xl font-semibold text-white">{{ connectedCount }} / {{ instances.length }}</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-blue-900/50 rounded-lg">
|
|
<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 class="bg-gray-800 rounded-lg p-4 border border-gray-700">
|
|
<div class="flex items-center gap-3">
|
|
<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>
|
|
|
|
<!-- 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>
|
|
|
|
<UButton
|
|
v-if="instance.status !== 'disconnected'"
|
|
size="sm"
|
|
color="neutral"
|
|
variant="soft"
|
|
@click="resetInstanceHandler(instance.id)"
|
|
>
|
|
Reiniciar
|
|
</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>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
definePageMeta({
|
|
layout: 'dashboard',
|
|
title: 'Dashboard',
|
|
})
|
|
|
|
const { instances, loading, fetchInstances, createInstance: apiCreateInstance, connectInstance: apiConnect, resetInstance: apiReset } = useInstances()
|
|
|
|
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)
|
|
// Refresh to get updated status/QR
|
|
await fetchInstances()
|
|
}
|
|
|
|
const resetInstanceHandler = async (id: string) => {
|
|
await apiReset(id)
|
|
}
|
|
|
|
const showQR = (instance: any) => {
|
|
currentQR.value = instance.qr_code
|
|
showQRModal.value = true
|
|
}
|
|
|
|
// Fetch instances on mount
|
|
onMounted(() => {
|
|
fetchInstances()
|
|
})
|
|
</script>
|