226 lines
9.4 KiB
Vue
226 lines
9.4 KiB
Vue
<template>
|
||
<div class="card hover:shadow-lg transition-shadow">
|
||
<div class="flex flex-col sm:flex-row gap-3 sm:gap-4">
|
||
<!-- Imagen del artículo -->
|
||
<div class="flex-shrink-0 self-center sm:self-start">
|
||
<div v-if="article.images && article.images.length > 0" class="w-24 h-24 sm:w-32 sm:h-32 relative">
|
||
<img
|
||
:src="article.images[0]"
|
||
:alt="article.title || 'Sin título'"
|
||
class="w-24 h-24 sm:w-32 sm:h-32 object-cover rounded-lg"
|
||
@error="($event) => handleImageError($event)"
|
||
/>
|
||
</div>
|
||
<div v-else class="w-24 h-24 sm:w-32 sm:h-32 bg-gray-200 dark:bg-gray-700 rounded-lg flex items-center justify-center">
|
||
<span class="text-gray-400 dark:text-gray-500 text-xs">Sin imagen</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Información del artículo -->
|
||
<div class="flex-1 min-w-0">
|
||
<div class="flex items-start justify-between mb-2">
|
||
<div class="flex-1 min-w-0">
|
||
<div class="flex flex-wrap items-center gap-2 mb-2">
|
||
<span
|
||
class="px-2 py-1 text-xs font-semibold rounded flex-shrink-0"
|
||
:class="
|
||
article.platform === 'wallapop'
|
||
? 'bg-blue-100 dark:bg-blue-900/50 text-blue-800 dark:text-blue-300'
|
||
: 'bg-green-100 dark:bg-green-900/50 text-green-800 dark:text-green-300'
|
||
"
|
||
>
|
||
{{ article.platform?.toUpperCase() || 'N/A' }}
|
||
</span>
|
||
<span v-if="article.username" class="px-2 py-1 text-xs font-medium rounded bg-purple-100 dark:bg-purple-900/50 text-purple-800 dark:text-purple-300 flex-shrink-0" title="Usuario">
|
||
👤 {{ article.username }}
|
||
</span>
|
||
<span v-if="article.worker_name" class="px-2 py-1 text-xs font-medium rounded bg-orange-100 dark:bg-orange-900/50 text-orange-800 dark:text-orange-300 flex-shrink-0" title="Worker">
|
||
⚙️ {{ article.worker_name }}
|
||
</span>
|
||
<span v-if="article.notifiedAt" class="text-xs sm:text-sm text-gray-500 dark:text-gray-400">
|
||
{{ formatDate(article.notifiedAt) }}
|
||
</span>
|
||
<span v-if="article.addedAt && !article.notifiedAt" class="text-xs sm:text-sm text-gray-500 dark:text-gray-400">
|
||
Añadido: {{ formatDate(article.addedAt) }}
|
||
</span>
|
||
</div>
|
||
|
||
<h3 class="text-base sm:text-lg font-semibold text-gray-900 dark:text-gray-100 mb-1 line-clamp-2" :title="article.title">
|
||
{{ article.title || 'Sin título' }}
|
||
</h3>
|
||
|
||
<div v-if="article.price !== null && article.price !== undefined" class="mb-2">
|
||
<span class="text-xl font-bold text-primary-600 dark:text-primary-400">
|
||
{{ article.price }} {{ article.currency || '€' }}
|
||
</span>
|
||
</div>
|
||
|
||
<div class="space-y-1 text-xs sm:text-sm text-gray-600 dark:text-gray-400 mb-2">
|
||
<div v-if="article.location" class="flex flex-wrap items-center">
|
||
<span class="font-medium">📍 Localidad:</span>
|
||
<span class="ml-2">{{ article.location }}</span>
|
||
</div>
|
||
<div v-if="article.allows_shipping !== null && article.allows_shipping !== undefined" class="flex flex-wrap items-center">
|
||
<span class="font-medium">🚚 Envío:</span>
|
||
<span class="ml-2">{{ article.allows_shipping ? '✅ Acepta envíos' : '❌ No acepta envíos' }}</span>
|
||
</div>
|
||
<div v-if="article.modified_at" class="flex flex-wrap items-center">
|
||
<span class="font-medium">🕒 Modificado:</span>
|
||
<span class="ml-2 break-all">{{ article.modified_at }}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<p v-if="article.description" class="text-xs sm:text-sm text-gray-700 dark:text-gray-300 mb-2 overflow-hidden line-clamp-2">
|
||
{{ article.description }}
|
||
</p>
|
||
|
||
<div class="flex flex-wrap items-center gap-2 sm:gap-4 mt-3">
|
||
<a
|
||
v-if="article.url"
|
||
:href="article.url"
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
class="text-primary-600 dark:text-primary-400 hover:text-primary-700 dark:hover:text-primary-300 text-xs sm:text-sm font-medium break-all"
|
||
>
|
||
🔗 Ver anuncio
|
||
</a>
|
||
<span class="text-xs text-gray-400 dark:text-gray-500 break-all">
|
||
ID: {{ article.id }}
|
||
</span>
|
||
<button
|
||
v-if="showRemoveButton"
|
||
@click="$emit('remove', article.platform, article.id)"
|
||
class="btn btn-danger text-xs sm:text-sm"
|
||
>
|
||
Eliminar
|
||
</button>
|
||
<button
|
||
v-if="!showRemoveButton && isAuthenticated && !isAdding"
|
||
@click="handleAddFavorite"
|
||
class="btn text-xs sm:text-sm flex items-center gap-1"
|
||
:class="favoriteStatus ? 'btn-secondary' : 'bg-pink-500 hover:bg-pink-600 text-white border-pink-600'"
|
||
:disabled="favoriteStatus"
|
||
:title="favoriteStatus ? 'Ya está en favoritos' : 'Añadir a favoritos'"
|
||
>
|
||
<HeartIconSolid v-if="favoriteStatus" class="w-4 h-4" />
|
||
<HeartIcon v-else class="w-4 h-4" />
|
||
{{ favoriteStatus ? 'En favoritos' : 'Añadir a favoritos' }}
|
||
</button>
|
||
<button
|
||
v-if="!showRemoveButton && isAuthenticated && isAdding"
|
||
disabled
|
||
class="btn btn-secondary text-xs sm:text-sm opacity-50 cursor-not-allowed"
|
||
>
|
||
<span class="inline-block animate-spin mr-1">⏳</span>
|
||
Añadiendo...
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { defineProps, defineEmits, ref, onMounted, onUnmounted } from 'vue';
|
||
import { HeartIcon } from '@heroicons/vue/24/outline';
|
||
import { HeartIcon as HeartIconSolid } from '@heroicons/vue/24/solid';
|
||
import authService from '../services/auth';
|
||
import api from '../services/api';
|
||
|
||
const props = defineProps({
|
||
article: {
|
||
type: Object,
|
||
required: true,
|
||
},
|
||
showRemoveButton: {
|
||
type: Boolean,
|
||
default: false,
|
||
},
|
||
isFavorite: {
|
||
type: Boolean,
|
||
default: false,
|
||
},
|
||
});
|
||
|
||
const emit = defineEmits(['remove', 'added']);
|
||
|
||
const isAdding = ref(false);
|
||
const isAuthenticated = ref(false);
|
||
const favoriteStatus = ref(props.isFavorite);
|
||
|
||
// Verificar autenticación al montar y cuando cambie
|
||
function checkAuth() {
|
||
isAuthenticated.value = authService.hasCredentials();
|
||
}
|
||
|
||
function formatDate(timestamp) {
|
||
if (!timestamp) return 'N/A';
|
||
return new Date(timestamp).toLocaleString('es-ES');
|
||
}
|
||
|
||
function handleImageError(event) {
|
||
// Si la imagen falla al cargar, reemplazar con placeholder
|
||
event.target.onerror = null; // Prevenir bucle infinito
|
||
event.target.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTI4IiBoZWlnaHQ9IjEyOCIgdmlld0JveD0iMCAwIDEyOCAxMjgiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxMjgiIGhlaWdodD0iMTI4IiBmaWxsPSIjRjNGNEY2Ii8+CjxwYXRoIGQ9Ik00OCA0OEg4ME04MCA4MEg0OE00OCA0OEw2NCA2NEw4MCA0OE00OCA4MEw2NCA2NE04MCA4MEw2NCA2NEw0OCA4MCIgc3Ryb2tlPSIjOUI5Q0E0IiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8L3N2Zz4K';
|
||
}
|
||
|
||
async function handleAddFavorite() {
|
||
if (!isAuthenticated.value || favoriteStatus.value || isAdding.value) {
|
||
return;
|
||
}
|
||
|
||
if (!props.article.platform || !props.article.id) {
|
||
alert('Error: El artículo no tiene platform o id válidos');
|
||
return;
|
||
}
|
||
|
||
isAdding.value = true;
|
||
|
||
try {
|
||
// El backend solo necesita platform e id
|
||
const favorite = {
|
||
platform: props.article.platform,
|
||
id: String(props.article.id), // Asegurar que sea string
|
||
};
|
||
|
||
await api.addFavorite(favorite);
|
||
favoriteStatus.value = true;
|
||
|
||
// Emitir evento para que el componente padre pueda actualizar si es necesario
|
||
emit('added', props.article.platform, props.article.id);
|
||
} catch (error) {
|
||
console.error('Error añadiendo a favoritos:', error);
|
||
// El interceptor de API ya maneja el error 401 mostrando el modal de login
|
||
if (error.response?.status === 404) {
|
||
alert('El artículo no se encontró en la base de datos. Asegúrate de que el artículo esté en la lista de notificados.');
|
||
} else if (error.response?.status === 400) {
|
||
alert('Error: ' + (error.response?.data?.error || 'Datos inválidos'));
|
||
} else if (error.response?.status !== 401) {
|
||
const errorMessage = error.response?.data?.error || error.message || 'Error desconocido';
|
||
alert('Error al añadir a favoritos: ' + errorMessage);
|
||
}
|
||
} finally {
|
||
isAdding.value = false;
|
||
}
|
||
}
|
||
|
||
function handleAuthChange() {
|
||
checkAuth();
|
||
}
|
||
|
||
onMounted(() => {
|
||
checkAuth();
|
||
// Escuchar cambios en la autenticación
|
||
window.addEventListener('auth-login', handleAuthChange);
|
||
window.addEventListener('auth-logout', handleAuthChange);
|
||
});
|
||
|
||
onUnmounted(() => {
|
||
window.removeEventListener('auth-login', handleAuthChange);
|
||
window.removeEventListener('auth-logout', handleAuthChange);
|
||
});
|
||
</script>
|
||
|