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
208 lines
4.9 KiB
TypeScript
208 lines
4.9 KiB
TypeScript
/**
|
|
* 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)
|
|
* Solo muestra contactos con teléfono registrado
|
|
*/
|
|
const filteredContacts = computed(() => {
|
|
// Primero filtrar solo contactos con teléfono
|
|
let result = contacts.value.filter(c => c.telefono && c.telefono.trim() !== '')
|
|
|
|
// 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
|
|
}
|
|
}
|