All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m0s
- Renombrar componentes: ContactsFilters→Filters, ContactItem→Item, ContactsList→List - Actualizar referencias en List.vue y app.vue - Filtrar solo contactos con teléfono registrado - Los filtros ahora deberían mostrarse correctamente
202 lines
5.2 KiB
Vue
202 lines
5.2 KiB
Vue
<template>
|
|
<div class="contacts-container">
|
|
<!-- Header -->
|
|
<div class="contacts-header">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h2 class="contacts-title">
|
|
<UIcon name="i-heroicons-users" class="w-6 h-6" />
|
|
Contactos
|
|
</h2>
|
|
<span v-if="filteredContacts.length > 0" class="contact-counter">
|
|
{{ filteredContacts.length }}
|
|
</span>
|
|
</div>
|
|
|
|
<!-- Filtros -->
|
|
<ContactsFilters
|
|
v-model="filters"
|
|
@clear="clearFilters"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Estados -->
|
|
<div v-if="isLoading" class="empty-state">
|
|
<UIcon name="i-heroicons-arrow-path" class="w-12 h-12 animate-spin text-primary" />
|
|
<p class="mt-4 text-gray-600 dark:text-gray-400">Cargando contactos...</p>
|
|
</div>
|
|
|
|
<div v-else-if="error" class="empty-state">
|
|
<UIcon name="i-heroicons-exclamation-triangle" class="w-16 h-16 text-error" />
|
|
<p class="mt-4 text-error font-semibold text-lg">Error al cargar contactos</p>
|
|
<p class="text-sm text-gray-500 mt-2">{{ error }}</p>
|
|
<UButton class="mt-4" variant="soft" @click="fetchContacts">
|
|
Reintentar
|
|
</UButton>
|
|
</div>
|
|
|
|
<div v-else-if="contacts.length === 0" class="empty-state">
|
|
<UIcon name="i-heroicons-user-group" class="w-16 h-16 text-gray-400" />
|
|
<p class="mt-4 text-gray-600 dark:text-gray-400 text-lg">No hay contactos disponibles</p>
|
|
</div>
|
|
|
|
<div v-else-if="filteredContacts.length === 0" class="empty-state">
|
|
<UIcon name="i-heroicons-funnel" class="w-16 h-16 text-gray-400" />
|
|
<p class="mt-4 text-gray-600 dark:text-gray-400 text-lg">No se encontraron contactos</p>
|
|
<p class="text-sm text-gray-500 mt-2">Prueba ajustando los filtros</p>
|
|
</div>
|
|
|
|
<!-- Lista de contactos -->
|
|
<div v-else class="contacts-list">
|
|
<ContactsItem
|
|
v-for="contact in filteredContacts"
|
|
:key="contact.id"
|
|
:contact="contact"
|
|
:alias="aliases[contact.id.toString()]"
|
|
@update-alias="handleUpdateAlias"
|
|
@click="handleContactClick(contact)"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import type { Contact } from '~/composables/useContacts'
|
|
|
|
const {
|
|
contacts,
|
|
aliases,
|
|
filters,
|
|
filteredContacts,
|
|
isLoading,
|
|
error,
|
|
fetchContacts,
|
|
fetchAliases,
|
|
updateAlias,
|
|
getWhatsAppUrl,
|
|
clearFilters
|
|
} = useContacts()
|
|
|
|
// Cargar datos al montar
|
|
onMounted(async () => {
|
|
await Promise.all([fetchContacts(), fetchAliases()])
|
|
})
|
|
|
|
// Manejar actualización de alias
|
|
const handleUpdateAlias = async ({ contactId, alias }: { contactId: number; alias: string }) => {
|
|
await updateAlias(contactId, alias)
|
|
}
|
|
|
|
// Click en contacto abre WhatsApp
|
|
const handleContactClick = (contact: Contact) => {
|
|
const url = getWhatsAppUrl(contact.telefono)
|
|
if (url) {
|
|
window.open(url, '_blank')
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.contacts-container {
|
|
background: rgba(255, 255, 255, 0.35);
|
|
backdrop-filter: blur(20px) saturate(180%);
|
|
border-radius: 1.5rem;
|
|
padding: 2rem;
|
|
box-shadow:
|
|
0 8px 32px 0 rgba(31, 38, 135, 0.15),
|
|
inset 0 1px 1px 0 rgba(255, 255, 255, 0.3);
|
|
border: 1px solid rgba(255, 255, 255, 0.18);
|
|
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
|
}
|
|
|
|
.contacts-header {
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
.contacts-title {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
font-size: 1.75rem;
|
|
font-weight: 700;
|
|
color: var(--color-gray-900);
|
|
margin: 0;
|
|
}
|
|
|
|
.contact-counter {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
min-width: 2.5rem;
|
|
height: 2.5rem;
|
|
padding: 0 0.75rem;
|
|
font-size: 1.125rem;
|
|
font-weight: 700;
|
|
border-radius: 0.875rem;
|
|
background: rgba(var(--color-primary-500), 0.15);
|
|
backdrop-filter: blur(10px) saturate(150%);
|
|
border: 1px solid rgba(var(--color-primary-500), 0.3);
|
|
color: rgb(var(--color-primary-500));
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
box-shadow:
|
|
0 2px 8px 0 rgba(var(--color-primary-500), 0.2),
|
|
inset 0 1px 1px 0 rgba(255, 255, 255, 0.3),
|
|
inset 0 -1px 2px 0 rgba(var(--color-primary-500), 0.1);
|
|
}
|
|
|
|
.empty-state {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 4rem 2rem;
|
|
text-align: center;
|
|
}
|
|
|
|
.contacts-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.75rem;
|
|
}
|
|
|
|
/* Responsive */
|
|
@media (max-width: 640px) {
|
|
.contacts-container {
|
|
padding: 1.5rem;
|
|
}
|
|
|
|
.contacts-title {
|
|
font-size: 1.5rem;
|
|
}
|
|
|
|
.contacts-list {
|
|
gap: 0.5rem;
|
|
}
|
|
}
|
|
</style>
|
|
|
|
<style>
|
|
/* Modo oscuro */
|
|
.dark .contacts-container {
|
|
background: rgba(0, 0, 0, 0.15) !important;
|
|
box-shadow:
|
|
0 8px 32px 0 rgba(0, 0, 0, 0.5),
|
|
inset 0 1px 1px 0 rgba(255, 255, 255, 0.05) !important;
|
|
border: 1px solid rgba(255, 255, 255, 0.08) !important;
|
|
}
|
|
|
|
.dark .contacts-title {
|
|
color: var(--color-gray-100) !important;
|
|
}
|
|
|
|
.dark .contact-counter {
|
|
background: rgba(var(--color-primary-500), 0.25) !important;
|
|
border-color: rgba(var(--color-primary-500), 0.5) !important;
|
|
color: rgb(var(--color-primary-400)) !important;
|
|
box-shadow:
|
|
0 2px 8px 0 rgba(var(--color-primary-500), 0.4),
|
|
0 0 0 1px rgba(var(--color-primary-500), 0.3),
|
|
inset 0 1px 1px 0 rgba(255, 255, 255, 0.08),
|
|
inset 0 -1px 2px 0 rgba(var(--color-primary-500), 0.2) !important;
|
|
}
|
|
</style>
|