Agregar sección Contactos con UTabs y conexión a Metabase
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 2m37s
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 2m37s
- Implementar UTabs (Contactos, Aplicaciones, Perfil) en app.vue - Crear componentes ContactsList, ContactsFilters, ContactItem - Agregar server routes para obtener contactos via Metabase API - Sistema de aliases por usuario guardados en archivos JSON - Filtros: nombre (fuzzy search), ID, teléfono, empleado - Click en contacto abre WhatsApp - Estilo glassmorphism consistente con la app
This commit is contained in:
205
nuxt4/app/composables/useContacts.ts
Normal file
205
nuxt4/app/composables/useContacts.ts
Normal file
@@ -0,0 +1,205 @@
|
||||
/**
|
||||
* Composable para gestión de contactos
|
||||
* Maneja fetching, filtrado, aliases y acciones
|
||||
*/
|
||||
export interface Contact {
|
||||
id: number
|
||||
name: string
|
||||
cedula: number | null
|
||||
ubicacion: string | null
|
||||
grupo_estudio: string | null
|
||||
empleado: boolean
|
||||
avatar_url: string | null
|
||||
telefono: string | null
|
||||
idciat: string | null
|
||||
}
|
||||
|
||||
export interface ContactFilters {
|
||||
search: string
|
||||
id: string
|
||||
telefono: string
|
||||
empleado: boolean
|
||||
}
|
||||
|
||||
export const useContacts = () => {
|
||||
const { fuzzyMatch, fuzzyScore } = useFuzzySearch()
|
||||
|
||||
// Estado
|
||||
const contacts = ref<Contact[]>([])
|
||||
const aliases = ref<Record<string, string>>({})
|
||||
const isLoading = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
// Filtros con persistencia en cookie
|
||||
const filtersCookie = useCookie<ContactFilters>('contact-filters', {
|
||||
maxAge: 60 * 60 * 24 * 7, // 1 semana
|
||||
default: () => ({
|
||||
search: '',
|
||||
id: '',
|
||||
telefono: '',
|
||||
empleado: true
|
||||
})
|
||||
})
|
||||
|
||||
const filters = ref<ContactFilters>({ ...filtersCookie.value })
|
||||
|
||||
// Sincronizar filtros con cookie
|
||||
watch(filters, (newFilters) => {
|
||||
filtersCookie.value = newFilters
|
||||
}, { deep: true })
|
||||
|
||||
/**
|
||||
* Cargar contactos desde el servidor
|
||||
*/
|
||||
const fetchContacts = async () => {
|
||||
isLoading.value = true
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
const params = new URLSearchParams()
|
||||
params.set('empleado', filters.value.empleado.toString())
|
||||
|
||||
// Filtro por ID se aplica en servidor si es exacto
|
||||
if (filters.value.id && !isNaN(parseInt(filters.value.id))) {
|
||||
params.set('id', filters.value.id)
|
||||
}
|
||||
|
||||
const data = await $fetch<Contact[]>(`/api/contacts?${params.toString()}`)
|
||||
contacts.value = data
|
||||
} catch (err: any) {
|
||||
console.error('Error al cargar contactos:', err)
|
||||
error.value = err.message || 'Error al cargar contactos'
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cargar aliases del usuario
|
||||
*/
|
||||
const fetchAliases = async () => {
|
||||
try {
|
||||
const data = await $fetch<Record<string, string>>('/api/contacts/aliases')
|
||||
aliases.value = data
|
||||
} catch (err: any) {
|
||||
console.error('Error al cargar aliases:', err)
|
||||
// No es crítico, continuar sin aliases
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Actualizar alias de un contacto
|
||||
*/
|
||||
const updateAlias = async (contactId: number, alias: string): Promise<boolean> => {
|
||||
try {
|
||||
await $fetch(`/api/contacts/aliases/${contactId}`, {
|
||||
method: 'PUT',
|
||||
body: { alias }
|
||||
})
|
||||
|
||||
// Actualizar estado local
|
||||
if (alias) {
|
||||
aliases.value[contactId.toString()] = alias
|
||||
} else {
|
||||
delete aliases.value[contactId.toString()]
|
||||
}
|
||||
|
||||
return true
|
||||
} catch (err: any) {
|
||||
console.error('Error al actualizar alias:', err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Contactos filtrados (búsqueda fuzzy en cliente)
|
||||
*/
|
||||
const filteredContacts = computed(() => {
|
||||
let result = contacts.value
|
||||
|
||||
// Filtro por teléfono (parcial)
|
||||
if (filters.value.telefono) {
|
||||
const telFilter = filters.value.telefono.replace(/\D/g, '')
|
||||
result = result.filter(c =>
|
||||
c.telefono?.replace(/\D/g, '').includes(telFilter)
|
||||
)
|
||||
}
|
||||
|
||||
// Filtro por nombre/alias (fuzzy)
|
||||
if (filters.value.search) {
|
||||
result = result
|
||||
.map(contact => ({
|
||||
contact,
|
||||
score: Math.max(
|
||||
fuzzyScore(contact.name, filters.value.search),
|
||||
fuzzyScore(aliases.value[contact.id.toString()] || '', filters.value.search)
|
||||
)
|
||||
}))
|
||||
.filter(({ score }) => score > 0)
|
||||
.sort((a, b) => b.score - a.score)
|
||||
.map(({ contact }) => contact)
|
||||
}
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
/**
|
||||
* Obtener nombre visible (alias o nombre real)
|
||||
*/
|
||||
const getDisplayName = (contact: Contact): string => {
|
||||
return aliases.value[contact.id.toString()] || contact.name
|
||||
}
|
||||
|
||||
/**
|
||||
* Verificar si el contacto tiene alias
|
||||
*/
|
||||
const hasAlias = (contact: Contact): boolean => {
|
||||
return !!aliases.value[contact.id.toString()]
|
||||
}
|
||||
|
||||
/**
|
||||
* Generar URL de WhatsApp
|
||||
*/
|
||||
const getWhatsAppUrl = (telefono: string | null): string | null => {
|
||||
if (!telefono) return null
|
||||
const cleanNumber = telefono.replace(/\D/g, '')
|
||||
if (!cleanNumber) return null
|
||||
return `https://wa.me/${cleanNumber}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Limpiar filtros
|
||||
*/
|
||||
const clearFilters = () => {
|
||||
filters.value = {
|
||||
search: '',
|
||||
id: '',
|
||||
telefono: '',
|
||||
empleado: true
|
||||
}
|
||||
}
|
||||
|
||||
// Watch para recargar cuando cambia el filtro de empleado o ID
|
||||
watch(
|
||||
() => [filters.value.empleado, filters.value.id],
|
||||
() => {
|
||||
fetchContacts()
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
contacts,
|
||||
aliases,
|
||||
filters,
|
||||
filteredContacts,
|
||||
isLoading,
|
||||
error,
|
||||
fetchContacts,
|
||||
fetchAliases,
|
||||
updateAlias,
|
||||
getDisplayName,
|
||||
hasAlias,
|
||||
getWhatsAppUrl,
|
||||
clearFilters
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user