Files
perfil/nuxt4/app/components/contacts/Item.vue
josedario87 39a01d351b
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m0s
Corregir componentes de contactos y filtrar solo con teléfono
- 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
2025-12-05 12:10:16 -06:00

318 lines
6.6 KiB
Vue

<template>
<div
class="contact-item"
:class="{ 'contact-item-clickable': contact.telefono }"
@click="handleClick"
>
<div class="contact-content">
<!-- Avatar -->
<div class="contact-avatar">
<img
v-if="contact.avatar_url && !avatarError"
:src="contact.avatar_url"
:alt="displayName"
class="avatar-img"
@error="avatarError = true"
/>
<UIcon v-else name="i-heroicons-user-circle" class="avatar-icon" />
</div>
<!-- Info -->
<div class="contact-info">
<div class="contact-name-row">
<span class="contact-name">{{ displayName }}</span>
<UBadge v-if="hasAlias" size="xs" color="primary" variant="soft">
Alias
</UBadge>
</div>
<span v-if="contact.telefono" class="contact-phone">
<UIcon name="i-heroicons-phone" class="phone-icon" />
{{ contact.telefono }}
</span>
<span v-if="contact.ubicacion" class="contact-location">
<UIcon name="i-heroicons-map-pin" class="location-icon" />
{{ contact.ubicacion }}
</span>
</div>
<!-- Acciones -->
<div class="contact-actions" @click.stop>
<UButton
icon="i-heroicons-pencil-square"
variant="ghost"
size="xs"
color="neutral"
@click="openAliasModal"
title="Editar alias"
/>
<UIcon
v-if="contact.telefono"
name="i-heroicons-chat-bubble-left-ellipsis"
class="whatsapp-icon"
/>
</div>
</div>
<!-- Modal para editar alias -->
<UModal v-model:open="showAliasModal">
<template #content>
<div class="alias-modal">
<h3 class="alias-modal-title">Editar alias</h3>
<p class="alias-modal-original">
<span class="label">Nombre original:</span>
{{ contact.name }}
</p>
<UFormField label="Alias personalizado">
<UInput
v-model="newAlias"
placeholder="Escribe un alias..."
maxlength="50"
class="alias-input"
/>
</UFormField>
<p class="alias-modal-hint">
Máximo 50 caracteres. Deja vacío para eliminar el alias.
</p>
<div class="alias-modal-actions">
<UButton variant="ghost" color="neutral" @click="showAliasModal = false">
Cancelar
</UButton>
<UButton :loading="saving" @click="saveAlias">
Guardar
</UButton>
</div>
</div>
</template>
</UModal>
</div>
</template>
<script setup lang="ts">
import type { Contact } from '~/composables/useContacts'
const props = defineProps<{
contact: Contact
alias?: string
}>()
const emit = defineEmits<{
'update-alias': [{ contactId: number; alias: string }]
'click': []
}>()
const toast = useToast()
// Estado local
const avatarError = ref(false)
const showAliasModal = ref(false)
const newAlias = ref('')
const saving = ref(false)
// Computed
const displayName = computed(() => props.alias || props.contact.name)
const hasAlias = computed(() => !!props.alias)
// Métodos
const handleClick = () => {
if (props.contact.telefono) {
emit('click')
}
}
const openAliasModal = () => {
newAlias.value = props.alias || ''
showAliasModal.value = true
}
const saveAlias = async () => {
saving.value = true
try {
emit('update-alias', {
contactId: props.contact.id,
alias: newAlias.value.trim()
})
showAliasModal.value = false
toast.add({
title: newAlias.value.trim() ? 'Alias guardado' : 'Alias eliminado',
color: 'success'
})
} finally {
saving.value = false
}
}
</script>
<style scoped>
.contact-item {
padding: 1rem;
border-radius: 1rem;
background: rgba(255, 255, 255, 0.3);
backdrop-filter: blur(15px) saturate(180%);
border: 1px solid rgba(255, 255, 255, 0.18);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.contact-item-clickable {
cursor: pointer;
}
.contact-item-clickable:hover {
transform: translateY(-2px);
box-shadow: 0 8px 24px 0 rgba(var(--color-primary-500), 0.2);
border-color: rgba(var(--color-primary-500), 0.4);
}
.contact-content {
display: flex;
gap: 1rem;
align-items: center;
}
.contact-avatar {
flex-shrink: 0;
width: 3rem;
height: 3rem;
border-radius: 50%;
background: rgba(var(--color-primary-500), 0.1);
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.avatar-img {
width: 100%;
height: 100%;
object-fit: cover;
}
.avatar-icon {
width: 2rem;
height: 2rem;
color: rgb(var(--color-primary-500));
}
.contact-info {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.contact-name-row {
display: flex;
align-items: center;
gap: 0.5rem;
flex-wrap: wrap;
}
.contact-name {
font-weight: 600;
color: var(--color-gray-900);
font-size: 0.9375rem;
}
.contact-phone,
.contact-location {
display: flex;
align-items: center;
gap: 0.375rem;
font-size: 0.8125rem;
color: var(--color-gray-600);
}
.phone-icon,
.location-icon {
width: 0.875rem;
height: 0.875rem;
flex-shrink: 0;
}
.contact-actions {
display: flex;
align-items: center;
gap: 0.5rem;
flex-shrink: 0;
}
.whatsapp-icon {
width: 1.25rem;
height: 1.25rem;
color: #25D366;
}
/* Modal de alias */
.alias-modal {
padding: 1.5rem;
display: flex;
flex-direction: column;
gap: 1rem;
}
.alias-modal-title {
font-size: 1.125rem;
font-weight: 600;
color: var(--color-gray-900);
margin: 0;
}
.alias-modal-original {
font-size: 0.875rem;
color: var(--color-gray-600);
margin: 0;
}
.alias-modal-original .label {
font-weight: 500;
color: var(--color-gray-700);
}
.alias-input {
width: 100%;
}
.alias-modal-hint {
font-size: 0.75rem;
color: var(--color-gray-500);
margin: 0;
}
.alias-modal-actions {
display: flex;
justify-content: flex-end;
gap: 0.75rem;
margin-top: 0.5rem;
}
</style>
<style>
/* Modo oscuro */
.dark .contact-item {
background: rgba(0, 0, 0, 0.3) !important;
border: 1px solid rgba(255, 255, 255, 0.1) !important;
}
.dark .contact-item-clickable:hover {
box-shadow: 0 8px 24px 0 rgba(var(--color-primary-500), 0.3) !important;
border-color: rgba(var(--color-primary-500), 0.5) !important;
}
.dark .contact-name {
color: var(--color-gray-100) !important;
}
.dark .contact-phone,
.dark .contact-location {
color: var(--color-gray-400) !important;
}
.dark .alias-modal-title {
color: var(--color-gray-100) !important;
}
.dark .alias-modal-original .label {
color: var(--color-gray-300) !important;
}
</style>