Implement dark mode support across the application
This commit is contained in:
@@ -1,19 +1,19 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="min-h-screen bg-gray-50">
|
<div class="min-h-screen bg-gray-50 dark:bg-gray-900">
|
||||||
<nav class="bg-white shadow-lg">
|
<nav class="bg-white dark:bg-gray-800 shadow-lg dark:shadow-gray-900 border-b border-gray-200 dark:border-gray-700">
|
||||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
<div class="flex justify-between h-16">
|
<div class="flex justify-between h-16">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div class="flex-shrink-0 flex items-center">
|
<div class="flex-shrink-0 flex items-center">
|
||||||
<h1 class="text-xl sm:text-2xl font-bold text-primary-600">🛎️ Wallabicher</h1>
|
<h1 class="text-xl sm:text-2xl font-bold text-primary-600 dark:text-primary-400">🛎️ Wallabicher</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="hidden md:ml-6 md:flex md:space-x-8">
|
<div class="hidden md:ml-6 md:flex md:space-x-8">
|
||||||
<router-link
|
<router-link
|
||||||
v-for="item in navItems"
|
v-for="item in navItems"
|
||||||
:key="item.path"
|
:key="item.path"
|
||||||
:to="item.path"
|
:to="item.path"
|
||||||
class="inline-flex items-center px-1 pt-1 text-sm font-medium text-gray-900 hover:text-primary-600 border-b-2 border-transparent hover:border-primary-600 transition-colors"
|
class="inline-flex items-center px-1 pt-1 text-sm font-medium text-gray-900 dark:text-gray-200 hover:text-primary-600 dark:hover:text-primary-400 border-b-2 border-transparent hover:border-primary-600 dark:hover:border-primary-400 transition-colors"
|
||||||
active-class="border-primary-600 text-primary-600"
|
active-class="border-primary-600 dark:border-primary-400 text-primary-600 dark:text-primary-400"
|
||||||
>
|
>
|
||||||
<component :is="item.icon" class="w-5 h-5 mr-2" />
|
<component :is="item.icon" class="w-5 h-5 mr-2" />
|
||||||
{{ item.name }}
|
{{ item.name }}
|
||||||
@@ -21,19 +21,27 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center space-x-3">
|
<div class="flex items-center space-x-3">
|
||||||
|
<button
|
||||||
|
@click="toggleDarkMode"
|
||||||
|
class="p-2 rounded-md text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||||||
|
title="Alternar modo oscuro"
|
||||||
|
>
|
||||||
|
<SunIcon v-if="isDark" class="w-5 h-5" />
|
||||||
|
<MoonIcon v-else class="w-5 h-5" />
|
||||||
|
</button>
|
||||||
<div class="hidden sm:flex items-center space-x-2">
|
<div class="hidden sm:flex items-center space-x-2">
|
||||||
<div
|
<div
|
||||||
class="w-3 h-3 rounded-full"
|
class="w-3 h-3 rounded-full"
|
||||||
:class="wsConnected ? 'bg-green-500' : 'bg-red-500'"
|
:class="wsConnected ? 'bg-green-500' : 'bg-red-500'"
|
||||||
></div>
|
></div>
|
||||||
<span class="text-sm text-gray-600">
|
<span class="text-sm text-gray-600 dark:text-gray-300">
|
||||||
{{ wsConnected ? 'Conectado' : 'Desconectado' }}
|
{{ wsConnected ? 'Conectado' : 'Desconectado' }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<!-- Mobile menu button -->
|
<!-- Mobile menu button -->
|
||||||
<button
|
<button
|
||||||
@click="mobileMenuOpen = !mobileMenuOpen"
|
@click="mobileMenuOpen = !mobileMenuOpen"
|
||||||
class="md:hidden inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-primary-500"
|
class="md:hidden inline-flex items-center justify-center p-2 rounded-md text-gray-400 dark:text-gray-500 hover:text-gray-500 dark:hover:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-primary-500"
|
||||||
aria-expanded="false"
|
aria-expanded="false"
|
||||||
>
|
>
|
||||||
<Bars3Icon v-if="!mobileMenuOpen" class="block h-6 w-6" />
|
<Bars3Icon v-if="!mobileMenuOpen" class="block h-6 w-6" />
|
||||||
@@ -44,27 +52,27 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Mobile menu -->
|
<!-- Mobile menu -->
|
||||||
<div v-if="mobileMenuOpen" class="md:hidden border-t border-gray-200">
|
<div v-if="mobileMenuOpen" class="md:hidden border-t border-gray-200 dark:border-gray-700">
|
||||||
<div class="pt-2 pb-3 space-y-1 px-4">
|
<div class="pt-2 pb-3 space-y-1 px-4">
|
||||||
<router-link
|
<router-link
|
||||||
v-for="item in navItems"
|
v-for="item in navItems"
|
||||||
:key="item.path"
|
:key="item.path"
|
||||||
:to="item.path"
|
:to="item.path"
|
||||||
@click="mobileMenuOpen = false"
|
@click="mobileMenuOpen = false"
|
||||||
class="flex items-center px-3 py-2 text-base font-medium text-gray-900 hover:text-primary-600 hover:bg-gray-50 rounded-md transition-colors"
|
class="flex items-center px-3 py-2 text-base font-medium text-gray-900 dark:text-gray-200 hover:text-primary-600 dark:hover:text-primary-400 hover:bg-gray-50 dark:hover:bg-gray-700 rounded-md transition-colors"
|
||||||
:class="$route.path === item.path ? 'text-primary-600 bg-gray-50' : ''"
|
:class="$route.path === item.path ? 'text-primary-600 dark:text-primary-400 bg-gray-50 dark:bg-gray-700' : ''"
|
||||||
>
|
>
|
||||||
<component :is="item.icon" class="w-5 h-5 mr-3" />
|
<component :is="item.icon" class="w-5 h-5 mr-3" />
|
||||||
{{ item.name }}
|
{{ item.name }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
<div class="pt-4 pb-3 border-t border-gray-200 px-4">
|
<div class="pt-4 pb-3 border-t border-gray-200 dark:border-gray-700 px-4">
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
<div
|
<div
|
||||||
class="w-3 h-3 rounded-full"
|
class="w-3 h-3 rounded-full"
|
||||||
:class="wsConnected ? 'bg-green-500' : 'bg-red-500'"
|
:class="wsConnected ? 'bg-green-500' : 'bg-red-500'"
|
||||||
></div>
|
></div>
|
||||||
<span class="text-sm text-gray-600">
|
<span class="text-sm text-gray-600 dark:text-gray-300">
|
||||||
{{ wsConnected ? 'Conectado' : 'Desconectado' }}
|
{{ wsConnected ? 'Conectado' : 'Desconectado' }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -79,7 +87,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, onUnmounted } from 'vue';
|
import { ref, onMounted, onUnmounted, computed } from 'vue';
|
||||||
import {
|
import {
|
||||||
HomeIcon,
|
HomeIcon,
|
||||||
DocumentTextIcon,
|
DocumentTextIcon,
|
||||||
@@ -88,6 +96,8 @@ import {
|
|||||||
DocumentMagnifyingGlassIcon,
|
DocumentMagnifyingGlassIcon,
|
||||||
Bars3Icon,
|
Bars3Icon,
|
||||||
XMarkIcon,
|
XMarkIcon,
|
||||||
|
SunIcon,
|
||||||
|
MoonIcon,
|
||||||
} from '@heroicons/vue/24/outline';
|
} from '@heroicons/vue/24/outline';
|
||||||
|
|
||||||
const navItems = [
|
const navItems = [
|
||||||
@@ -100,9 +110,35 @@ const navItems = [
|
|||||||
|
|
||||||
const wsConnected = ref(false);
|
const wsConnected = ref(false);
|
||||||
const mobileMenuOpen = ref(false);
|
const mobileMenuOpen = ref(false);
|
||||||
|
const darkMode = ref(false);
|
||||||
let ws = null;
|
let ws = null;
|
||||||
|
|
||||||
|
const isDark = computed(() => darkMode.value);
|
||||||
|
|
||||||
|
function toggleDarkMode() {
|
||||||
|
darkMode.value = !darkMode.value;
|
||||||
|
if (darkMode.value) {
|
||||||
|
document.documentElement.classList.add('dark');
|
||||||
|
localStorage.setItem('darkMode', 'true');
|
||||||
|
} else {
|
||||||
|
document.documentElement.classList.remove('dark');
|
||||||
|
localStorage.setItem('darkMode', 'false');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function initDarkMode() {
|
||||||
|
const saved = localStorage.getItem('darkMode');
|
||||||
|
if (saved === 'true' || (!saved && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||||
|
darkMode.value = true;
|
||||||
|
document.documentElement.classList.add('dark');
|
||||||
|
} else {
|
||||||
|
darkMode.value = false;
|
||||||
|
document.documentElement.classList.remove('dark');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
initDarkMode();
|
||||||
connectWebSocket();
|
connectWebSocket();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -4,13 +4,13 @@
|
|||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
body {
|
body {
|
||||||
@apply bg-gray-50 text-gray-900;
|
@apply bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer components {
|
@layer components {
|
||||||
.card {
|
.card {
|
||||||
@apply bg-white rounded-lg shadow-md p-6;
|
@apply bg-white dark:bg-gray-800 rounded-lg shadow-md dark:shadow-lg p-6 border border-gray-200 dark:border-gray-700;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
@@ -18,19 +18,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary {
|
.btn-primary {
|
||||||
@apply bg-primary-600 text-white hover:bg-primary-700;
|
@apply bg-primary-600 dark:bg-primary-500 text-white hover:bg-primary-700 dark:hover:bg-primary-600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-secondary {
|
.btn-secondary {
|
||||||
@apply bg-gray-200 text-gray-800 hover:bg-gray-300;
|
@apply bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 hover:bg-gray-300 dark:hover:bg-gray-600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-danger {
|
.btn-danger {
|
||||||
@apply bg-red-600 text-white hover:bg-red-700;
|
@apply bg-red-600 dark:bg-red-700 text-white hover:bg-red-700 dark:hover:bg-red-800;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input {
|
.input {
|
||||||
@apply w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent;
|
@apply w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Line clamp utility */
|
/* Line clamp utility */
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<div class="mb-4 sm:mb-6">
|
<div class="mb-4 sm:mb-6">
|
||||||
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-3 sm:gap-4 mb-4">
|
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-3 sm:gap-4 mb-4">
|
||||||
<h1 class="text-2xl sm:text-3xl font-bold text-gray-900">Artículos Notificados</h1>
|
<h1 class="text-2xl sm:text-3xl font-bold text-gray-900 dark:text-gray-100">Artículos Notificados</h1>
|
||||||
<div class="flex flex-col sm:flex-row items-stretch sm:items-center gap-2 sm:space-x-4">
|
<div class="flex flex-col sm:flex-row items-stretch sm:items-center gap-2 sm:space-x-4">
|
||||||
<select
|
<select
|
||||||
v-model="selectedPlatform"
|
v-model="selectedPlatform"
|
||||||
@@ -40,20 +40,20 @@
|
|||||||
|
|
||||||
<div v-if="loading" class="text-center py-12">
|
<div v-if="loading" class="text-center py-12">
|
||||||
<div class="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-primary-600"></div>
|
<div class="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-primary-600"></div>
|
||||||
<p class="mt-2 text-gray-600">Cargando artículos...</p>
|
<p class="mt-2 text-gray-600 dark:text-gray-400">Cargando artículos...</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="filteredArticles.length === 0 && !searchQuery" class="card text-center py-12">
|
<div v-else-if="filteredArticles.length === 0 && !searchQuery" class="card text-center py-12">
|
||||||
<p class="text-gray-600">No hay artículos para mostrar</p>
|
<p class="text-gray-600 dark:text-gray-400">No hay artículos para mostrar</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="searching" class="card text-center py-12">
|
<div v-else-if="searching" class="card text-center py-12">
|
||||||
<div class="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-primary-600"></div>
|
<div class="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-primary-600"></div>
|
||||||
<p class="mt-2 text-gray-600">Buscando artículos en Redis...</p>
|
<p class="mt-2 text-gray-600 dark:text-gray-400">Buscando artículos en Redis...</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="filteredArticles.length === 0 && searchQuery && !searching" class="card text-center py-12">
|
<div v-else-if="filteredArticles.length === 0 && searchQuery && !searching" class="card text-center py-12">
|
||||||
<p class="text-gray-600">No se encontraron artículos que coincidan con "{{ searchQuery }}"</p>
|
<p class="text-gray-600 dark:text-gray-400">No se encontraron artículos que coincidan con "{{ searchQuery }}"</p>
|
||||||
<button @click="searchQuery = ''" class="btn btn-secondary mt-4">Limpiar búsqueda</button>
|
<button @click="searchQuery = ''" class="btn btn-secondary mt-4">Limpiar búsqueda</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -74,8 +74,8 @@
|
|||||||
@error="($event) => handleImageError($event)"
|
@error="($event) => handleImageError($event)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="w-24 h-24 sm:w-32 sm:h-32 bg-gray-200 rounded-lg flex items-center justify-center">
|
<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 text-xs">Sin imagen</span>
|
<span class="text-gray-400 dark:text-gray-500 text-xs">Sin imagen</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -94,12 +94,12 @@
|
|||||||
>
|
>
|
||||||
{{ article.platform?.toUpperCase() || 'N/A' }}
|
{{ article.platform?.toUpperCase() || 'N/A' }}
|
||||||
</span>
|
</span>
|
||||||
<span class="text-xs sm:text-sm text-gray-500">
|
<span class="text-xs sm:text-sm text-gray-500 dark:text-gray-400">
|
||||||
{{ formatDate(article.notifiedAt) }}
|
{{ formatDate(article.notifiedAt) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 class="text-base sm:text-lg font-semibold text-gray-900 mb-1 line-clamp-2" :title="article.title">
|
<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' }}
|
{{ article.title || 'Sin título' }}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
@@ -109,7 +109,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-1 text-xs sm:text-sm text-gray-600 mb-2">
|
<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">
|
<div v-if="article.location" class="flex flex-wrap items-center">
|
||||||
<span class="font-medium">📍 Localidad:</span>
|
<span class="font-medium">📍 Localidad:</span>
|
||||||
<span class="ml-2">{{ article.location }}</span>
|
<span class="ml-2">{{ article.location }}</span>
|
||||||
@@ -124,7 +124,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p v-if="article.description" class="text-xs sm:text-sm text-gray-700 mb-2 overflow-hidden line-clamp-2">
|
<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 }}
|
{{ article.description }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -138,7 +138,7 @@
|
|||||||
>
|
>
|
||||||
🔗 Ver anuncio
|
🔗 Ver anuncio
|
||||||
</a>
|
</a>
|
||||||
<span class="text-xs text-gray-400 break-all">
|
<span class="text-xs text-gray-400 dark:text-gray-500 break-all">
|
||||||
ID: {{ article.id }}
|
ID: {{ article.id }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-2xl sm:text-3xl font-bold text-gray-900 mb-4 sm:mb-6">Dashboard</h1>
|
<h1 class="text-2xl sm:text-3xl font-bold text-gray-900 dark:text-gray-100 mb-4 sm:mb-6">Dashboard</h1>
|
||||||
|
|
||||||
<!-- Estadísticas -->
|
<!-- Estadísticas -->
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 sm:gap-6 mb-6 sm:mb-8">
|
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 sm:gap-6 mb-6 sm:mb-8">
|
||||||
@@ -10,8 +10,8 @@
|
|||||||
<Cog6ToothIcon class="w-6 h-6 text-primary-600" />
|
<Cog6ToothIcon class="w-6 h-6 text-primary-600" />
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-3 sm:ml-4">
|
<div class="ml-3 sm:ml-4">
|
||||||
<p class="text-xs sm:text-sm font-medium text-gray-600">Workers Activos</p>
|
<p class="text-xs sm:text-sm font-medium text-gray-600 dark:text-gray-400">Workers Activos</p>
|
||||||
<p class="text-xl sm:text-2xl font-bold text-gray-900">{{ stats.activeWorkers }}/{{ stats.totalWorkers }}</p>
|
<p class="text-xl sm:text-2xl font-bold text-gray-900 dark:text-gray-100">{{ stats.activeWorkers }}/{{ stats.totalWorkers }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -22,8 +22,8 @@
|
|||||||
<HeartIcon class="w-6 h-6 text-green-600" />
|
<HeartIcon class="w-6 h-6 text-green-600" />
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-4">
|
<div class="ml-4">
|
||||||
<p class="text-sm font-medium text-gray-600">Favoritos</p>
|
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Favoritos</p>
|
||||||
<p class="text-2xl font-bold text-gray-900">{{ stats.totalFavorites }}</p>
|
<p class="text-2xl font-bold text-gray-900 dark:text-gray-100">{{ stats.totalFavorites }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -34,8 +34,8 @@
|
|||||||
<DocumentTextIcon class="w-6 h-6 text-blue-600" />
|
<DocumentTextIcon class="w-6 h-6 text-blue-600" />
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-4">
|
<div class="ml-4">
|
||||||
<p class="text-sm font-medium text-gray-600">Artículos Notificados</p>
|
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Artículos Notificados</p>
|
||||||
<p class="text-2xl font-bold text-gray-900">{{ stats.totalNotified }}</p>
|
<p class="text-2xl font-bold text-gray-900 dark:text-gray-100">{{ stats.totalNotified }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -46,8 +46,8 @@
|
|||||||
<ChartBarIcon class="w-6 h-6 text-purple-600" />
|
<ChartBarIcon class="w-6 h-6 text-purple-600" />
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-4">
|
<div class="ml-4">
|
||||||
<p class="text-sm font-medium text-gray-600">Plataformas</p>
|
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Plataformas</p>
|
||||||
<p class="text-sm font-bold text-gray-900">
|
<p class="text-sm font-bold text-gray-900 dark:text-gray-100">
|
||||||
W: {{ stats.platforms?.wallapop || 0 }} | V: {{ stats.platforms?.vinted || 0 }}
|
W: {{ stats.platforms?.wallapop || 0 }} | V: {{ stats.platforms?.vinted || 0 }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -58,14 +58,14 @@
|
|||||||
<!-- Gráfico de plataformas -->
|
<!-- Gráfico de plataformas -->
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4 sm:gap-6">
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4 sm:gap-6">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2 class="text-lg sm:text-xl font-bold text-gray-900 mb-3 sm:mb-4">Distribución por Plataforma</h2>
|
<h2 class="text-lg sm:text-xl font-bold text-gray-900 dark:text-gray-100 mb-3 sm:mb-4">Distribución por Plataforma</h2>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<div class="flex justify-between mb-2">
|
<div class="flex justify-between mb-2">
|
||||||
<span class="text-sm font-medium text-gray-700">Wallapop</span>
|
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">Wallapop</span>
|
||||||
<span class="text-sm font-medium text-gray-900">{{ stats.platforms?.wallapop || 0 }}</span>
|
<span class="text-sm font-medium text-gray-900 dark:text-gray-100">{{ stats.platforms?.wallapop || 0 }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full bg-gray-200 rounded-full h-2">
|
<div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2">
|
||||||
<div
|
<div
|
||||||
class="bg-primary-600 h-2 rounded-full"
|
class="bg-primary-600 h-2 rounded-full"
|
||||||
:style="{
|
:style="{
|
||||||
@@ -76,10 +76,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="flex justify-between mb-2">
|
<div class="flex justify-between mb-2">
|
||||||
<span class="text-sm font-medium text-gray-700">Vinted</span>
|
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">Vinted</span>
|
||||||
<span class="text-sm font-medium text-gray-900">{{ stats.platforms?.vinted || 0 }}</span>
|
<span class="text-sm font-medium text-gray-900 dark:text-gray-100">{{ stats.platforms?.vinted || 0 }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full bg-gray-200 rounded-full h-2">
|
<div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2">
|
||||||
<div
|
<div
|
||||||
class="bg-green-600 h-2 rounded-full"
|
class="bg-green-600 h-2 rounded-full"
|
||||||
:style="{
|
:style="{
|
||||||
@@ -92,28 +92,28 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2 class="text-lg sm:text-xl font-bold text-gray-900 mb-3 sm:mb-4">Accesos Rápidos</h2>
|
<h2 class="text-lg sm:text-xl font-bold text-gray-900 dark:text-gray-100 mb-3 sm:mb-4">Accesos Rápidos</h2>
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<router-link
|
<router-link
|
||||||
to="/articles"
|
to="/articles"
|
||||||
class="flex items-center justify-between p-3 bg-gray-50 rounded-lg hover:bg-gray-100 transition-colors"
|
class="flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-700 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-600 transition-colors"
|
||||||
>
|
>
|
||||||
<span class="text-sm font-medium text-gray-700">Ver todos los artículos</span>
|
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">Ver todos los artículos</span>
|
||||||
<ArrowRightIcon class="w-5 h-5 text-gray-400" />
|
<ArrowRightIcon class="w-5 h-5 text-gray-400 dark:text-gray-500" />
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link
|
<router-link
|
||||||
to="/favorites"
|
to="/favorites"
|
||||||
class="flex items-center justify-between p-3 bg-gray-50 rounded-lg hover:bg-gray-100 transition-colors"
|
class="flex items-center justify-between p-3 bg-gray-50 rounded-lg hover:bg-gray-100 transition-colors"
|
||||||
>
|
>
|
||||||
<span class="text-sm font-medium text-gray-700">Ver favoritos</span>
|
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">Ver favoritos</span>
|
||||||
<ArrowRightIcon class="w-5 h-5 text-gray-400" />
|
<ArrowRightIcon class="w-5 h-5 text-gray-400 dark:text-gray-500" />
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link
|
<router-link
|
||||||
to="/workers"
|
to="/workers"
|
||||||
class="flex items-center justify-between p-3 bg-gray-50 rounded-lg hover:bg-gray-100 transition-colors"
|
class="flex items-center justify-between p-3 bg-gray-50 rounded-lg hover:bg-gray-100 transition-colors"
|
||||||
>
|
>
|
||||||
<span class="text-sm font-medium text-gray-700">Gestionar workers</span>
|
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">Gestionar workers</span>
|
||||||
<ArrowRightIcon class="w-5 h-5 text-gray-400" />
|
<ArrowRightIcon class="w-5 h-5 text-gray-400 dark:text-gray-500" />
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-3 sm:gap-4 mb-4 sm:mb-6">
|
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-3 sm:gap-4 mb-4 sm:mb-6">
|
||||||
<h1 class="text-2xl sm:text-3xl font-bold text-gray-900">Favoritos</h1>
|
<h1 class="text-2xl sm:text-3xl font-bold text-gray-900 dark:text-gray-100">Favoritos</h1>
|
||||||
<button @click="loadFavorites" class="btn btn-primary self-start sm:self-auto">
|
<button @click="loadFavorites" class="btn btn-primary self-start sm:self-auto">
|
||||||
Actualizar
|
Actualizar
|
||||||
</button>
|
</button>
|
||||||
@@ -9,13 +9,13 @@
|
|||||||
|
|
||||||
<div v-if="loading" class="text-center py-12">
|
<div v-if="loading" class="text-center py-12">
|
||||||
<div class="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-primary-600"></div>
|
<div class="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-primary-600"></div>
|
||||||
<p class="mt-2 text-gray-600">Cargando favoritos...</p>
|
<p class="mt-2 text-gray-600 dark:text-gray-400">Cargando favoritos...</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="favorites.length === 0" class="card text-center py-12">
|
<div v-else-if="favorites.length === 0" class="card text-center py-12">
|
||||||
<HeartIcon class="w-16 h-16 text-gray-300 mx-auto mb-4" />
|
<HeartIcon class="w-16 h-16 text-gray-300 dark:text-gray-600 mx-auto mb-4" />
|
||||||
<p class="text-gray-600 text-lg">No tienes favoritos aún</p>
|
<p class="text-gray-600 dark:text-gray-400 text-lg">No tienes favoritos aún</p>
|
||||||
<p class="text-gray-400 text-sm mt-2">
|
<p class="text-gray-400 dark:text-gray-500 text-sm mt-2">
|
||||||
Los artículos que marques como favoritos aparecerán aquí
|
Los artículos que marques como favoritos aparecerán aquí
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -40,17 +40,17 @@
|
|||||||
{{ favorite.platform?.toUpperCase() || 'N/A' }}
|
{{ favorite.platform?.toUpperCase() || 'N/A' }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<h3 class="text-lg font-semibold text-gray-900 mb-2">
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-2">
|
||||||
{{ favorite.title || 'Sin título' }}
|
{{ favorite.title || 'Sin título' }}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-sm text-gray-600 mb-2">
|
<p class="text-sm text-gray-600 dark:text-gray-400 mb-2">
|
||||||
{{ favorite.description?.substring(0, 100) }}...
|
{{ favorite.description?.substring(0, 100) }}...
|
||||||
</p>
|
</p>
|
||||||
<div class="flex items-center justify-between mt-4">
|
<div class="flex items-center justify-between mt-4">
|
||||||
<span class="text-xl font-bold text-primary-600">
|
<span class="text-xl font-bold text-primary-600">
|
||||||
{{ favorite.price }} {{ favorite.currency }}
|
{{ favorite.price }} {{ favorite.currency }}
|
||||||
</span>
|
</span>
|
||||||
<span class="text-sm text-gray-500">
|
<span class="text-sm text-gray-500 dark:text-gray-400">
|
||||||
{{ favorite.location }}
|
{{ favorite.location }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -82,7 +82,7 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="text-xs text-gray-400 mt-2">
|
<p class="text-xs text-gray-400 dark:text-gray-500 mt-2">
|
||||||
Añadido: {{ formatDate(favorite.addedAt) }}
|
Añadido: {{ formatDate(favorite.addedAt) }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<div class="flex flex-col gap-4 mb-4 sm:mb-6">
|
<div class="flex flex-col gap-4 mb-4 sm:mb-6">
|
||||||
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-3 sm:gap-4">
|
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-3 sm:gap-4">
|
||||||
<h1 class="text-2xl sm:text-3xl font-bold text-gray-900">Logs del Sistema</h1>
|
<h1 class="text-2xl sm:text-3xl font-bold text-gray-900 dark:text-gray-100">Logs del Sistema</h1>
|
||||||
<div class="flex flex-col sm:flex-row items-stretch sm:items-center gap-2 sm:space-x-4">
|
<div class="flex flex-col sm:flex-row items-stretch sm:items-center gap-2 sm:space-x-4">
|
||||||
<select v-model="logLevel" @change="loadLogs(followLatestLog)" class="input text-sm sm:text-base" style="width: 100%; min-width: 160px;">
|
<select v-model="logLevel" @change="loadLogs(followLatestLog)" class="input text-sm sm:text-base" style="width: 100%; min-width: 160px;">
|
||||||
<option value="">Todos los niveles</option>
|
<option value="">Todos los niveles</option>
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Panel de configuración de auto-refresh -->
|
<!-- Panel de configuración de auto-refresh -->
|
||||||
<div class="card p-4 bg-gray-50 border border-gray-200">
|
<div class="card p-4 bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-700">
|
||||||
<div class="flex flex-col sm:flex-row sm:items-center gap-3 sm:gap-4">
|
<div class="flex flex-col sm:flex-row sm:items-center gap-3 sm:gap-4">
|
||||||
<label class="flex items-center gap-2 cursor-pointer">
|
<label class="flex items-center gap-2 cursor-pointer">
|
||||||
<input
|
<input
|
||||||
@@ -27,12 +27,12 @@
|
|||||||
@change="handleAutoRefreshChange"
|
@change="handleAutoRefreshChange"
|
||||||
class="w-4 h-4 text-blue-600 rounded focus:ring-blue-500"
|
class="w-4 h-4 text-blue-600 rounded focus:ring-blue-500"
|
||||||
>
|
>
|
||||||
<span class="font-medium text-gray-700">Auto-refresh</span>
|
<span class="font-medium text-gray-700 dark:text-gray-300">Auto-refresh</span>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<div v-if="autoRefresh" class="flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-4 flex-1">
|
<div v-if="autoRefresh" class="flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-4 flex-1">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<label class="text-sm text-gray-600 whitespace-nowrap">Intervalo:</label>
|
<label class="text-sm text-gray-600 dark:text-gray-400 whitespace-nowrap">Intervalo:</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
v-model.number="refreshIntervalSeconds"
|
v-model.number="refreshIntervalSeconds"
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
max="300"
|
max="300"
|
||||||
class="input text-sm w-20"
|
class="input text-sm w-20"
|
||||||
>
|
>
|
||||||
<span class="text-sm text-gray-600 whitespace-nowrap">segundos</span>
|
<span class="text-sm text-gray-600 dark:text-gray-400 whitespace-nowrap">segundos</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<label class="flex items-center gap-2 cursor-pointer">
|
<label class="flex items-center gap-2 cursor-pointer">
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
v-model="followLatestLog"
|
v-model="followLatestLog"
|
||||||
class="w-4 h-4 text-blue-600 rounded focus:ring-blue-500"
|
class="w-4 h-4 text-blue-600 rounded focus:ring-blue-500"
|
||||||
>
|
>
|
||||||
<span class="text-sm text-gray-700">Seguir último log</span>
|
<span class="text-sm text-gray-700 dark:text-gray-300">Seguir último log</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-3 sm:gap-4 mb-4 sm:mb-6">
|
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-3 sm:gap-4 mb-4 sm:mb-6">
|
||||||
<h1 class="text-2xl sm:text-3xl font-bold text-gray-900">Gestión de Workers</h1>
|
<h1 class="text-2xl sm:text-3xl font-bold text-gray-900 dark:text-gray-100">Gestión de Workers</h1>
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
<button @click="showGeneralModal = true" class="btn btn-secondary text-xs sm:text-sm">
|
<button @click="showGeneralModal = true" class="btn btn-secondary text-xs sm:text-sm">
|
||||||
⚙️ Configuración General
|
⚙️ Configuración General
|
||||||
@@ -17,13 +17,13 @@
|
|||||||
|
|
||||||
<div v-if="loading" class="text-center py-12">
|
<div v-if="loading" class="text-center py-12">
|
||||||
<div class="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-primary-600"></div>
|
<div class="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-primary-600"></div>
|
||||||
<p class="mt-2 text-gray-600">Cargando workers...</p>
|
<p class="mt-2 text-gray-600 dark:text-gray-400">Cargando workers...</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else class="space-y-4">
|
<div v-else class="space-y-4">
|
||||||
<!-- Workers activos -->
|
<!-- Workers activos -->
|
||||||
<div v-if="activeWorkers.length > 0">
|
<div v-if="activeWorkers.length > 0">
|
||||||
<h2 class="text-xl font-semibold text-gray-900 mb-4">Workers Activos ({{ activeWorkers.length }})</h2>
|
<h2 class="text-xl font-semibold text-gray-900 dark:text-gray-100 mb-4">Workers Activos ({{ activeWorkers.length }})</h2>
|
||||||
<div class="grid grid-cols-1 gap-4">
|
<div class="grid grid-cols-1 gap-4">
|
||||||
<div
|
<div
|
||||||
v-for="(worker, index) in activeWorkers"
|
v-for="(worker, index) in activeWorkers"
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
<div class="flex items-start justify-between">
|
<div class="flex items-start justify-between">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<div class="flex items-center space-x-2 mb-3">
|
<div class="flex items-center space-x-2 mb-3">
|
||||||
<h3 class="text-lg font-semibold text-gray-900">{{ worker.name }}</h3>
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">{{ worker.name }}</h3>
|
||||||
<span class="px-2 py-1 text-xs font-semibold rounded bg-green-100 text-green-800">
|
<span class="px-2 py-1 text-xs font-semibold rounded bg-green-100 text-green-800">
|
||||||
Activo
|
Activo
|
||||||
</span>
|
</span>
|
||||||
@@ -72,12 +72,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Filtros aplicados -->
|
<!-- Filtros aplicados -->
|
||||||
<div v-if="hasFilters(worker)" class="mt-3 pt-3 border-t border-gray-200">
|
<div v-if="hasFilters(worker)" class="mt-3 pt-3 border-t border-gray-200 dark:border-gray-700">
|
||||||
<details class="text-xs">
|
<details class="text-xs">
|
||||||
<summary class="cursor-pointer text-gray-600 hover:text-gray-900 font-medium">
|
<summary class="cursor-pointer text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 font-medium">
|
||||||
Ver filtros ({{ countFilters(worker) }})
|
Ver filtros ({{ countFilters(worker) }})
|
||||||
</summary>
|
</summary>
|
||||||
<div class="mt-2 space-y-1 text-gray-600">
|
<div class="mt-2 space-y-1 text-gray-600 dark:text-gray-400">
|
||||||
<div v-if="worker.title_exclude?.length" class="flex items-start">
|
<div v-if="worker.title_exclude?.length" class="flex items-start">
|
||||||
<span class="font-medium mr-2">Excluir título:</span>
|
<span class="font-medium mr-2">Excluir título:</span>
|
||||||
<span>{{ worker.title_exclude.join(', ') }}</span>
|
<span>{{ worker.title_exclude.join(', ') }}</span>
|
||||||
@@ -129,7 +129,7 @@
|
|||||||
|
|
||||||
<!-- Workers desactivados -->
|
<!-- Workers desactivados -->
|
||||||
<div v-if="disabledWorkers.length > 0" class="mt-6 sm:mt-8">
|
<div v-if="disabledWorkers.length > 0" class="mt-6 sm:mt-8">
|
||||||
<h2 class="text-lg sm:text-xl font-semibold text-gray-900 mb-3 sm:mb-4">Workers Desactivados ({{ disabledWorkers.length }})</h2>
|
<h2 class="text-lg sm:text-xl font-semibold text-gray-900 dark:text-gray-100 mb-3 sm:mb-4">Workers Desactivados ({{ disabledWorkers.length }})</h2>
|
||||||
<div class="grid grid-cols-1 gap-4">
|
<div class="grid grid-cols-1 gap-4">
|
||||||
<div
|
<div
|
||||||
v-for="(worker, index) in disabledWorkers"
|
v-for="(worker, index) in disabledWorkers"
|
||||||
@@ -139,7 +139,7 @@
|
|||||||
<div class="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-3">
|
<div class="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-3">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<div class="flex flex-wrap items-center gap-2 mb-2">
|
<div class="flex flex-wrap items-center gap-2 mb-2">
|
||||||
<h3 class="text-base sm:text-lg font-semibold text-gray-900">{{ worker.name }}</h3>
|
<h3 class="text-base sm:text-lg font-semibold text-gray-900 dark:text-gray-100">{{ worker.name }}</h3>
|
||||||
<span class="px-2 py-1 text-xs font-semibold rounded bg-red-100 text-red-800">
|
<span class="px-2 py-1 text-xs font-semibold rounded bg-red-100 text-red-800">
|
||||||
Desactivado
|
Desactivado
|
||||||
</span>
|
</span>
|
||||||
@@ -147,7 +147,7 @@
|
|||||||
{{ (worker.platform || 'wallapop').toUpperCase() }}
|
{{ (worker.platform || 'wallapop').toUpperCase() }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-xs sm:text-sm text-gray-600">{{ worker.search_query }}</p>
|
<p class="text-xs sm:text-sm text-gray-600 dark:text-gray-400">{{ worker.search_query }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-wrap gap-2 sm:space-x-2 sm:ml-4">
|
<div class="flex flex-wrap gap-2 sm:space-x-2 sm:ml-4">
|
||||||
<button
|
<button
|
||||||
@@ -175,7 +175,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="activeWorkers.length === 0 && disabledWorkers.length === 0" class="card text-center py-12">
|
<div v-if="activeWorkers.length === 0 && disabledWorkers.length === 0" class="card text-center py-12">
|
||||||
<p class="text-gray-600">No hay workers configurados</p>
|
<p class="text-gray-600 dark:text-gray-400">No hay workers configurados</p>
|
||||||
<button @click="showAddModal = true" class="btn btn-primary mt-4">
|
<button @click="showAddModal = true" class="btn btn-primary mt-4">
|
||||||
+ Crear primer worker
|
+ Crear primer worker
|
||||||
</button>
|
</button>
|
||||||
@@ -185,50 +185,50 @@
|
|||||||
<!-- Modal para añadir/editar worker -->
|
<!-- Modal para añadir/editar worker -->
|
||||||
<div
|
<div
|
||||||
v-if="showAddModal || editingWorker"
|
v-if="showAddModal || editingWorker"
|
||||||
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-2 sm:p-4"
|
class="fixed inset-0 bg-black bg-opacity-50 dark:bg-opacity-70 flex items-center justify-center z-50 p-2 sm:p-4"
|
||||||
@click.self="closeModal"
|
@click.self="closeModal"
|
||||||
>
|
>
|
||||||
<div class="bg-white rounded-lg p-4 sm:p-6 max-w-4xl w-full max-h-[95vh] sm:max-h-[90vh] overflow-y-auto">
|
<div class="bg-white dark:bg-gray-800 rounded-lg p-4 sm:p-6 max-w-4xl w-full max-h-[95vh] sm:max-h-[90vh] overflow-y-auto">
|
||||||
<h2 class="text-xl sm:text-2xl font-bold text-gray-900 mb-3 sm:mb-4">
|
<h2 class="text-xl sm:text-2xl font-bold text-gray-900 dark:text-gray-100 mb-3 sm:mb-4">
|
||||||
{{ editingWorker ? 'Editar Worker' : 'Añadir Worker' }}
|
{{ editingWorker ? 'Editar Worker' : 'Añadir Worker' }}
|
||||||
</h2>
|
</h2>
|
||||||
<form @submit.prevent="saveWorker" class="space-y-6">
|
<form @submit.prevent="saveWorker" class="space-y-6">
|
||||||
<!-- Información básica -->
|
<!-- Información básica -->
|
||||||
<div class="border-b border-gray-200 pb-3 sm:pb-4">
|
<div class="border-b border-gray-200 dark:border-gray-700 pb-3 sm:pb-4">
|
||||||
<h3 class="text-base sm:text-lg font-semibold text-gray-900 mb-3 sm:mb-4">Información Básica</h3>
|
<h3 class="text-base sm:text-lg font-semibold text-gray-900 dark:text-gray-100 mb-3 sm:mb-4">Información Básica</h3>
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3 sm:gap-4">
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3 sm:gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Nombre *</label>
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Nombre *</label>
|
||||||
<input v-model="workerForm.name" type="text" class="input" required />
|
<input v-model="workerForm.name" type="text" class="input" required />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Plataforma</label>
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Plataforma</label>
|
||||||
<select v-model="workerForm.platform" class="input">
|
<select v-model="workerForm.platform" class="input">
|
||||||
<option value="wallapop">Wallapop</option>
|
<option value="wallapop">Wallapop</option>
|
||||||
<option value="vinted">Vinted</option>
|
<option value="vinted">Vinted</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="md:col-span-2">
|
<div class="md:col-span-2">
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Búsqueda *</label>
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Búsqueda *</label>
|
||||||
<input v-model="workerForm.search_query" type="text" class="input" required placeholder="ej: playstation 1" />
|
<input v-model="workerForm.search_query" type="text" class="input" required placeholder="ej: playstation 1" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Precios y Thread -->
|
<!-- Precios y Thread -->
|
||||||
<div class="border-b border-gray-200 pb-3 sm:pb-4">
|
<div class="border-b border-gray-200 dark:border-gray-700 pb-3 sm:pb-4">
|
||||||
<h3 class="text-base sm:text-lg font-semibold text-gray-900 mb-3 sm:mb-4">Precios y Notificaciones</h3>
|
<h3 class="text-base sm:text-lg font-semibold text-gray-900 dark:text-gray-100 mb-3 sm:mb-4">Precios y Notificaciones</h3>
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 sm:gap-4">
|
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 sm:gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Precio Mínimo (€)</label>
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Precio Mínimo (€)</label>
|
||||||
<input v-model.number="workerForm.min_price" type="number" class="input" min="0" step="0.01" />
|
<input v-model.number="workerForm.min_price" type="number" class="input" min="0" step="0.01" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Precio Máximo (€)</label>
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Precio Máximo (€)</label>
|
||||||
<input v-model.number="workerForm.max_price" type="number" class="input" min="0" step="0.01" />
|
<input v-model.number="workerForm.max_price" type="number" class="input" min="0" step="0.01" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Thread ID (Telegram)</label>
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Thread ID (Telegram)</label>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<input v-model.number="workerForm.thread_id" type="number" class="input flex-1" placeholder="Ej: 8" />
|
<input v-model.number="workerForm.thread_id" type="number" class="input flex-1" placeholder="Ej: 8" />
|
||||||
<button
|
<button
|
||||||
@@ -240,20 +240,20 @@
|
|||||||
{{ loadingThreads ? 'Cargando...' : '📋 Obtener Threads' }}
|
{{ loadingThreads ? 'Cargando...' : '📋 Obtener Threads' }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-xs text-gray-500 mt-1">Opcional: ID del hilo donde enviar notificaciones</p>
|
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">Opcional: ID del hilo donde enviar notificaciones</p>
|
||||||
|
|
||||||
<!-- Lista de threads disponibles -->
|
<!-- Lista de threads disponibles -->
|
||||||
<div v-if="availableThreads.length > 0" class="mt-2 p-2 bg-gray-50 rounded border border-gray-200 max-h-40 overflow-y-auto">
|
<div v-if="availableThreads.length > 0" class="mt-2 p-2 bg-gray-50 dark:bg-gray-700 rounded border border-gray-200 dark:border-gray-600 max-h-40 overflow-y-auto">
|
||||||
<p class="text-xs font-medium text-gray-700 mb-2">Threads disponibles:</p>
|
<p class="text-xs font-medium text-gray-700 dark:text-gray-300 mb-2">Threads disponibles:</p>
|
||||||
<div
|
<div
|
||||||
v-for="thread in availableThreads"
|
v-for="thread in availableThreads"
|
||||||
:key="thread.id"
|
:key="thread.id"
|
||||||
@click="selectThread(thread.id)"
|
@click="selectThread(thread.id)"
|
||||||
class="flex items-center justify-between p-2 mb-1 bg-white rounded border border-gray-200 cursor-pointer hover:bg-gray-50 transition-colors"
|
class="flex items-center justify-between p-2 mb-1 bg-white dark:bg-gray-800 rounded border border-gray-200 dark:border-gray-600 cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
|
||||||
>
|
>
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<span class="text-sm font-medium text-gray-900">{{ thread.name }}</span>
|
<span class="text-sm font-medium text-gray-900 dark:text-gray-100">{{ thread.name }}</span>
|
||||||
<span class="text-xs text-gray-500 ml-2">ID: {{ thread.id }}</span>
|
<span class="text-xs text-gray-500 dark:text-gray-400 ml-2">ID: {{ thread.id }}</span>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -266,9 +266,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Mensaje informativo si no hay threads -->
|
<!-- Mensaje informativo si no hay threads -->
|
||||||
<div v-if="threadsMessage" class="mt-2 p-2 bg-blue-50 border border-blue-200 rounded">
|
<div v-if="threadsMessage" class="mt-2 p-2 bg-blue-50 dark:bg-blue-900/30 border border-blue-200 dark:border-blue-800 rounded">
|
||||||
<p class="text-xs text-blue-800">{{ threadsMessage }}</p>
|
<p class="text-xs text-blue-800 dark:text-blue-300">{{ threadsMessage }}</p>
|
||||||
<p v-if="threadsInfo" class="text-xs text-blue-700 mt-1">{{ threadsInfo }}</p>
|
<p v-if="threadsInfo" class="text-xs text-blue-700 dark:text-blue-400 mt-1">{{ threadsInfo }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -276,18 +276,18 @@
|
|||||||
|
|
||||||
<!-- Ubicación -->
|
<!-- Ubicación -->
|
||||||
<div class="border-b border-gray-200 pb-4">
|
<div class="border-b border-gray-200 pb-4">
|
||||||
<h3 class="text-lg font-semibold text-gray-900 mb-4">Búsqueda Local (Opcional)</h3>
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">Búsqueda Local (Opcional)</h3>
|
||||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Latitud</label>
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Latitud</label>
|
||||||
<input v-model.number="workerForm.latitude" type="number" class="input" step="any" />
|
<input v-model.number="workerForm.latitude" type="number" class="input" step="any" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Longitud</label>
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Longitud</label>
|
||||||
<input v-model.number="workerForm.longitude" type="number" class="input" step="any" />
|
<input v-model.number="workerForm.longitude" type="number" class="input" step="any" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Distancia Máxima (km)</label>
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Distancia Máxima (km)</label>
|
||||||
<input v-model.number="workerForm.max_distance" type="number" class="input" min="0" />
|
<input v-model.number="workerForm.max_distance" type="number" class="input" min="0" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -295,20 +295,20 @@
|
|||||||
|
|
||||||
<!-- Filtros de exclusión -->
|
<!-- Filtros de exclusión -->
|
||||||
<div class="border-b border-gray-200 pb-4">
|
<div class="border-b border-gray-200 pb-4">
|
||||||
<h3 class="text-lg font-semibold text-gray-900 mb-4">Filtros de Exclusión</h3>
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">Filtros de Exclusión</h3>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Excluir palabras del título</label>
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Excluir palabras del título</label>
|
||||||
<textarea
|
<textarea
|
||||||
v-model="workerForm.title_exclude_text"
|
v-model="workerForm.title_exclude_text"
|
||||||
class="input"
|
class="input"
|
||||||
rows="3"
|
rows="3"
|
||||||
placeholder="Una palabra por línea o separadas por comas"
|
placeholder="Una palabra por línea o separadas por comas"
|
||||||
></textarea>
|
></textarea>
|
||||||
<p class="text-xs text-gray-500 mt-1">Si aparece alguna palabra, se excluye el artículo</p>
|
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">Si aparece alguna palabra, se excluye el artículo</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Excluir palabras de la descripción</label>
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Excluir palabras de la descripción</label>
|
||||||
<textarea
|
<textarea
|
||||||
v-model="workerForm.description_exclude_text"
|
v-model="workerForm.description_exclude_text"
|
||||||
class="input"
|
class="input"
|
||||||
@@ -317,34 +317,34 @@
|
|||||||
></textarea>
|
></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Excluir si primera palabra del título es</label>
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Excluir si primera palabra del título es</label>
|
||||||
<textarea
|
<textarea
|
||||||
v-model="workerForm.title_first_word_exclude_text"
|
v-model="workerForm.title_first_word_exclude_text"
|
||||||
class="input"
|
class="input"
|
||||||
rows="2"
|
rows="2"
|
||||||
placeholder="Una palabra por línea o separadas por comas"
|
placeholder="Una palabra por línea o separadas por comas"
|
||||||
></textarea>
|
></textarea>
|
||||||
<p class="text-xs text-gray-500 mt-1">Ej: "Reacondicionado", "Vendido", etc.</p>
|
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">Ej: "Reacondicionado", "Vendido", etc.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Filtros de inclusión -->
|
<!-- Filtros de inclusión -->
|
||||||
<div class="border-b border-gray-200 pb-4">
|
<div class="border-b border-gray-200 pb-4">
|
||||||
<h3 class="text-lg font-semibold text-gray-900 mb-4">Filtros de Inclusión (Requeridos)</h3>
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">Filtros de Inclusión (Requeridos)</h3>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Palabras requeridas en el título</label>
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Palabras requeridas en el título</label>
|
||||||
<textarea
|
<textarea
|
||||||
v-model="workerForm.title_must_include_text"
|
v-model="workerForm.title_must_include_text"
|
||||||
class="input"
|
class="input"
|
||||||
rows="3"
|
rows="3"
|
||||||
placeholder="Una palabra por línea o separadas por comas"
|
placeholder="Una palabra por línea o separadas por comas"
|
||||||
></textarea>
|
></textarea>
|
||||||
<p class="text-xs text-gray-500 mt-1">TODAS las palabras deben aparecer</p>
|
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">TODAS las palabras deben aparecer</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Palabras requeridas en la descripción</label>
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Palabras requeridas en la descripción</label>
|
||||||
<textarea
|
<textarea
|
||||||
v-model="workerForm.description_must_include_text"
|
v-model="workerForm.description_must_include_text"
|
||||||
class="input"
|
class="input"
|
||||||
@@ -357,7 +357,7 @@
|
|||||||
|
|
||||||
<!-- Configuración avanzada -->
|
<!-- Configuración avanzada -->
|
||||||
<div class="border-b border-gray-200 pb-4">
|
<div class="border-b border-gray-200 pb-4">
|
||||||
<h3 class="text-lg font-semibold text-gray-900 mb-4">Configuración Avanzada</h3>
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">Configuración Avanzada</h3>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Intervalo de verificación (segundos)</label>
|
<label class="block text-sm font-medium text-gray-700 mb-1">Intervalo de verificación (segundos)</label>
|
||||||
<input v-model.number="workerForm.check_every" type="number" class="input" min="1" />
|
<input v-model.number="workerForm.check_every" type="number" class="input" min="1" />
|
||||||
@@ -380,12 +380,12 @@
|
|||||||
<!-- Modal para configuración general -->
|
<!-- Modal para configuración general -->
|
||||||
<div
|
<div
|
||||||
v-if="showGeneralModal"
|
v-if="showGeneralModal"
|
||||||
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-2 sm:p-4"
|
class="fixed inset-0 bg-black bg-opacity-50 dark:bg-opacity-70 flex items-center justify-center z-50 p-2 sm:p-4"
|
||||||
@click.self="closeGeneralModal"
|
@click.self="closeGeneralModal"
|
||||||
>
|
>
|
||||||
<div class="bg-white rounded-lg p-4 sm:p-6 max-w-2xl w-full max-h-[95vh] sm:max-h-[90vh] overflow-y-auto">
|
<div class="bg-white dark:bg-gray-800 rounded-lg p-4 sm:p-6 max-w-2xl w-full max-h-[95vh] sm:max-h-[90vh] overflow-y-auto">
|
||||||
<h2 class="text-xl sm:text-2xl font-bold text-gray-900 mb-3 sm:mb-4">Configuración General</h2>
|
<h2 class="text-xl sm:text-2xl font-bold text-gray-900 dark:text-gray-100 mb-3 sm:mb-4">Configuración General</h2>
|
||||||
<p class="text-sm text-gray-600 mb-4">Estas configuraciones se aplican a todos los workers</p>
|
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4">Estas configuraciones se aplican a todos los workers</p>
|
||||||
<form @submit.prevent="saveGeneralConfig" class="space-y-4">
|
<form @submit.prevent="saveGeneralConfig" class="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Excluir palabras del título (global)</label>
|
<label class="block text-sm font-medium text-gray-700 mb-1">Excluir palabras del título (global)</label>
|
||||||
@@ -405,7 +405,7 @@
|
|||||||
placeholder="Una palabra por línea o separadas por comas"
|
placeholder="Una palabra por línea o separadas por comas"
|
||||||
></textarea>
|
></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col-reverse sm:flex-row justify-end gap-2 sm:space-x-2 pt-4 border-t border-gray-200">
|
<div class="flex flex-col-reverse sm:flex-row justify-end gap-2 sm:space-x-2 pt-4 border-t border-gray-200 dark:border-gray-700">
|
||||||
<button type="button" @click="closeGeneralModal" class="btn btn-secondary text-sm sm:text-base">
|
<button type="button" @click="closeGeneralModal" class="btn btn-secondary text-sm sm:text-base">
|
||||||
Cancelar
|
Cancelar
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ export default {
|
|||||||
"./index.html",
|
"./index.html",
|
||||||
"./src/**/*.{vue,js,ts,jsx,tsx}",
|
"./src/**/*.{vue,js,ts,jsx,tsx}",
|
||||||
],
|
],
|
||||||
|
darkMode: 'class',
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
colors: {
|
colors: {
|
||||||
|
|||||||
Reference in New Issue
Block a user