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
318 lines
6.6 KiB
Vue
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>
|