UI: Agregar iconos a expandir/colapsar y notificacion al copiar
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m4s
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m4s
This commit is contained in:
@@ -109,8 +109,12 @@
|
|||||||
<div class="relative">
|
<div class="relative">
|
||||||
<button
|
<button
|
||||||
@click="toggleExpand(event.id)"
|
@click="toggleExpand(event.id)"
|
||||||
class="text-xs text-[var(--wa-text-muted)] hover:text-[var(--wa-text)] mb-1"
|
class="flex items-center gap-1 text-xs text-[var(--wa-text-muted)] hover:text-[var(--wa-text)] mb-1"
|
||||||
>
|
>
|
||||||
|
<UIcon
|
||||||
|
:name="expandedEvents.has(event.id) ? 'i-lucide-chevron-up' : 'i-lucide-chevron-down'"
|
||||||
|
class="w-3 h-3"
|
||||||
|
/>
|
||||||
{{ expandedEvents.has(event.id) ? 'Colapsar' : 'Expandir' }} datos
|
{{ expandedEvents.has(event.id) ? 'Colapsar' : 'Expandir' }} datos
|
||||||
</button>
|
</button>
|
||||||
<pre
|
<pre
|
||||||
@@ -253,11 +257,25 @@ const formatTime = (dateStr: string) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
const copyToClipboard = async (text: string) => {
|
const copyToClipboard = async (text: string) => {
|
||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(text)
|
await navigator.clipboard.writeText(text)
|
||||||
|
toast.add({
|
||||||
|
title: 'Copiado al portapapeles',
|
||||||
|
icon: 'i-lucide-check',
|
||||||
|
color: 'success',
|
||||||
|
timeout: 2000
|
||||||
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to copy:', err)
|
console.error('Failed to copy:', err)
|
||||||
|
toast.add({
|
||||||
|
title: 'Error al copiar',
|
||||||
|
icon: 'i-lucide-x',
|
||||||
|
color: 'error',
|
||||||
|
timeout: 2000
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -164,7 +164,30 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Messages -->
|
<!-- Messages -->
|
||||||
<div ref="messagesContainer" class="flex-1 overflow-y-auto p-4 space-y-2">
|
<div
|
||||||
|
ref="messagesContainer"
|
||||||
|
class="flex-1 overflow-y-auto p-4 space-y-2"
|
||||||
|
@scroll="handleScroll"
|
||||||
|
>
|
||||||
|
<!-- Load more indicator -->
|
||||||
|
<div
|
||||||
|
v-if="loadingMore"
|
||||||
|
class="flex justify-center py-2"
|
||||||
|
>
|
||||||
|
<UIcon name="i-lucide-loader-2" class="w-5 h-5 animate-spin text-[var(--wa-text-muted)]" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else-if="hasMoreMessages"
|
||||||
|
class="flex justify-center py-2"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="text-xs text-[var(--wa-text-muted)] hover:text-[var(--wa-text)]"
|
||||||
|
@click="loadMoreMessages"
|
||||||
|
>
|
||||||
|
Cargar mensajes anteriores
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<MessagesMessageBubble
|
<MessagesMessageBubble
|
||||||
v-for="message in reversedMessages"
|
v-for="message in reversedMessages"
|
||||||
:key="message.id"
|
:key="message.id"
|
||||||
@@ -220,6 +243,8 @@ const chats = ref<any[]>([])
|
|||||||
const messages = ref<any[]>([])
|
const messages = ref<any[]>([])
|
||||||
const loadingChats = ref(false)
|
const loadingChats = ref(false)
|
||||||
const loadingMessages = ref(false)
|
const loadingMessages = ref(false)
|
||||||
|
const loadingMore = ref(false)
|
||||||
|
const hasMoreMessages = ref(true)
|
||||||
const showDebugPanel = ref(false)
|
const showDebugPanel = ref(false)
|
||||||
const showChatsDebug = ref(false)
|
const showChatsDebug = ref(false)
|
||||||
const showSelectedChatDebug = ref(false)
|
const showSelectedChatDebug = ref(false)
|
||||||
@@ -264,8 +289,15 @@ watch(selectedChat, async (chat) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
loadingMessages.value = true
|
loadingMessages.value = true
|
||||||
|
hasMoreMessages.value = true // Reset for new chat
|
||||||
try {
|
try {
|
||||||
messages.value = await $fetch(`/api/messages/${selectedInstance.value.value}/${chat.id}`)
|
const result = await $fetch(`/api/messages/${selectedInstance.value.value}/${chat.id}?limit=50`)
|
||||||
|
messages.value = result as any[]
|
||||||
|
|
||||||
|
// If we got less than 50, there are no more messages
|
||||||
|
if ((result as any[]).length < 50) {
|
||||||
|
hasMoreMessages.value = false
|
||||||
|
}
|
||||||
|
|
||||||
// Subscribe to presence updates for this chat (if not a group)
|
// Subscribe to presence updates for this chat (if not a group)
|
||||||
if (!chat.isGroup && chat.jid) {
|
if (!chat.isGroup && chat.jid) {
|
||||||
@@ -279,6 +311,55 @@ watch(selectedChat, async (chat) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Load more messages (infinite scroll)
|
||||||
|
const loadMoreMessages = async () => {
|
||||||
|
if (!selectedInstance.value?.value || !selectedChat.value || loadingMore.value || !hasMoreMessages.value) return
|
||||||
|
|
||||||
|
const oldestMessage = messages.value[messages.value.length - 1] // Messages are in DESC order
|
||||||
|
if (!oldestMessage) return
|
||||||
|
|
||||||
|
loadingMore.value = true
|
||||||
|
const previousScrollHeight = messagesContainer.value?.scrollHeight || 0
|
||||||
|
|
||||||
|
try {
|
||||||
|
const instanceId = selectedInstance.value.value
|
||||||
|
const chatId = selectedChat.value.id
|
||||||
|
const before = oldestMessage.timestamp
|
||||||
|
|
||||||
|
const olderMessages = await $fetch(`/api/messages/${instanceId}/${chatId}?limit=50&before=${before}`)
|
||||||
|
|
||||||
|
if ((olderMessages as any[]).length < 50) {
|
||||||
|
hasMoreMessages.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((olderMessages as any[]).length > 0) {
|
||||||
|
messages.value = [...messages.value, ...(olderMessages as any[])]
|
||||||
|
|
||||||
|
// Maintain scroll position after loading
|
||||||
|
nextTick(() => {
|
||||||
|
if (messagesContainer.value) {
|
||||||
|
const newScrollHeight = messagesContainer.value.scrollHeight
|
||||||
|
messagesContainer.value.scrollTop = newScrollHeight - previousScrollHeight
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error loading more messages:', e)
|
||||||
|
} finally {
|
||||||
|
loadingMore.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle scroll for infinite scroll
|
||||||
|
const handleScroll = () => {
|
||||||
|
if (!messagesContainer.value) return
|
||||||
|
|
||||||
|
// Load more when scrolled near the top (< 100px)
|
||||||
|
if (messagesContainer.value.scrollTop < 100 && hasMoreMessages.value && !loadingMore.value) {
|
||||||
|
loadMoreMessages()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Computed presence text for current chat
|
// Computed presence text for current chat
|
||||||
const currentChatPresence = computed(() => {
|
const currentChatPresence = computed(() => {
|
||||||
if (!selectedChat.value?.jid) return null
|
if (!selectedChat.value?.jid) return null
|
||||||
@@ -484,5 +565,28 @@ onMounted(() => {
|
|||||||
console.log('[Messages] Message sent:', data)
|
console.log('[Messages] Message sent:', data)
|
||||||
// Already handled by the send function
|
// Already handled by the send function
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Listen for reaction updates
|
||||||
|
on('message.reaction', (data) => {
|
||||||
|
console.log('[Messages] Reaction received:', data)
|
||||||
|
|
||||||
|
// If it's for the current instance and chat, reload messages
|
||||||
|
if (data.instanceId === selectedInstance.value?.value && selectedChat.value) {
|
||||||
|
reloadMessages()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Listen for message status updates
|
||||||
|
on('message.status', (data) => {
|
||||||
|
console.log('[Messages] Message status update:', data)
|
||||||
|
|
||||||
|
// Update message status in local state
|
||||||
|
if (data.instanceId === selectedInstance.value?.value) {
|
||||||
|
const messageIndex = messages.value.findIndex(m => m.messageId === data.messageId)
|
||||||
|
if (messageIndex !== -1) {
|
||||||
|
messages.value[messageIndex].status = data.status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -29,10 +29,11 @@ export default defineEventHandler(async (event) => {
|
|||||||
throw createError({ statusCode: 404, message: 'Instance not found' })
|
throw createError({ statusCode: 404, message: 'Instance not found' })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get chats with last message
|
// Get chats with last message and type
|
||||||
const result = await query<ChatRow>(
|
const result = await query<ChatRow>(
|
||||||
`SELECT c.id, c.jid, c.name, c.is_group, c.unread_count, c.last_message_at,
|
`SELECT c.id, c.jid, c.name, c.is_group, c.unread_count, c.last_message_at,
|
||||||
(SELECT content FROM messages m WHERE m.chat_id = c.id ORDER BY timestamp DESC LIMIT 1) as last_message
|
c.last_message_type,
|
||||||
|
(SELECT COALESCE(content, caption) FROM messages m WHERE m.chat_id = c.id ORDER BY timestamp DESC LIMIT 1) as last_message
|
||||||
FROM chats c
|
FROM chats c
|
||||||
WHERE c.instance_id = $1
|
WHERE c.instance_id = $1
|
||||||
ORDER BY c.last_message_at DESC NULLS LAST`,
|
ORDER BY c.last_message_at DESC NULLS LAST`,
|
||||||
@@ -46,6 +47,7 @@ export default defineEventHandler(async (event) => {
|
|||||||
isGroup: row.is_group,
|
isGroup: row.is_group,
|
||||||
unreadCount: row.unread_count,
|
unreadCount: row.unread_count,
|
||||||
lastMessageAt: row.last_message_at,
|
lastMessageAt: row.last_message_at,
|
||||||
lastMessage: row.last_message || ''
|
lastMessage: row.last_message || '',
|
||||||
|
lastMessageType: row.last_message_type || 'text'
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user