- Fix YAML files with unquoted strings containing quotes - Export singleton instances in ai/__init__.py - Fix validator scoring prompt to use replace() instead of format() to avoid conflicts with JSON curly braces Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
251 lines
12 KiB
HTML
251 lines
12 KiB
HTML
{% extends "base.html" %}
|
||
|
||
{% block title %}Dashboard{% endblock %}
|
||
|
||
{% block content %}
|
||
<div class="animate-fade-in">
|
||
<!-- Header -->
|
||
<div class="flex items-center justify-between mb-8">
|
||
<div>
|
||
<h1 class="text-3xl font-bold">Dashboard</h1>
|
||
<p class="text-gray-400 mt-1">Bienvenido, {{ user.username }}</p>
|
||
</div>
|
||
<a href="/compose" class="btn-primary px-6 py-3 rounded-xl font-medium flex items-center gap-2 transition-all">
|
||
<span>✍️</span>
|
||
<span>Crear Post</span>
|
||
</a>
|
||
</div>
|
||
|
||
<!-- Stats Grid -->
|
||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
||
<div class="stat-card card rounded-2xl p-6">
|
||
<div class="flex items-center justify-between mb-4">
|
||
<div class="w-12 h-12 rounded-xl bg-blue-500/20 flex items-center justify-center">
|
||
<span class="text-2xl">📝</span>
|
||
</div>
|
||
<span class="text-xs text-gray-500 bg-dark-700 px-2 py-1 rounded-full">Hoy</span>
|
||
</div>
|
||
<p class="text-3xl font-bold">{{ stats.posts_today }}</p>
|
||
<p class="text-gray-400 text-sm mt-1">Posts publicados</p>
|
||
</div>
|
||
|
||
<div class="stat-card card rounded-2xl p-6">
|
||
<div class="flex items-center justify-between mb-4">
|
||
<div class="w-12 h-12 rounded-xl bg-purple-500/20 flex items-center justify-center">
|
||
<span class="text-2xl">📅</span>
|
||
</div>
|
||
<span class="text-xs text-gray-500 bg-dark-700 px-2 py-1 rounded-full">Semana</span>
|
||
</div>
|
||
<p class="text-3xl font-bold">{{ stats.posts_week }}</p>
|
||
<p class="text-gray-400 text-sm mt-1">Posts esta semana</p>
|
||
</div>
|
||
|
||
<div class="stat-card card rounded-2xl p-6">
|
||
<div class="flex items-center justify-between mb-4">
|
||
<div class="w-12 h-12 rounded-xl bg-yellow-500/20 flex items-center justify-center">
|
||
<span class="text-2xl">⏳</span>
|
||
</div>
|
||
<span class="text-xs text-yellow-400 bg-yellow-500/20 px-2 py-1 rounded-full">Pendiente</span>
|
||
</div>
|
||
<p class="text-3xl font-bold">{{ stats.pending_approval }}</p>
|
||
<p class="text-gray-400 text-sm mt-1">Por aprobar</p>
|
||
</div>
|
||
|
||
<div class="stat-card card rounded-2xl p-6">
|
||
<div class="flex items-center justify-between mb-4">
|
||
<div class="w-12 h-12 rounded-xl bg-green-500/20 flex items-center justify-center">
|
||
<span class="text-2xl">🗓️</span>
|
||
</div>
|
||
<span class="text-xs text-green-400 bg-green-500/20 px-2 py-1 rounded-full">Programado</span>
|
||
</div>
|
||
<p class="text-3xl font-bold">{{ stats.scheduled }}</p>
|
||
<p class="text-gray-400 text-sm mt-1">Posts programados</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Main Content Grid -->
|
||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||
<!-- Pending Posts -->
|
||
<div class="card rounded-2xl p-6">
|
||
<div class="flex items-center justify-between mb-6">
|
||
<h2 class="text-lg font-semibold flex items-center gap-2">
|
||
<span>⏳</span>
|
||
<span>Pendientes de Aprobación</span>
|
||
</h2>
|
||
<a href="/posts?status=pending" class="text-primary text-sm hover:underline">Ver todos</a>
|
||
</div>
|
||
<div class="space-y-4">
|
||
{% if pending_posts %}
|
||
{% for post in pending_posts %}
|
||
<div class="bg-dark-800/50 rounded-xl p-4 hover:bg-dark-700/50 transition-colors">
|
||
<p class="text-sm text-gray-300 line-clamp-2">{{ post.content[:100] }}{% if post.content|length > 100 %}...{% endif %}</p>
|
||
<div class="flex items-center justify-between mt-3">
|
||
<div class="flex items-center gap-2">
|
||
{% for platform in post.platforms %}
|
||
<span class="text-xs bg-dark-700 px-2 py-1 rounded-full">{{ platform }}</span>
|
||
{% endfor %}
|
||
</div>
|
||
<span class="text-xs text-gray-500">{{ post.created_at[:10] if post.created_at else '-' }}</span>
|
||
</div>
|
||
</div>
|
||
{% endfor %}
|
||
{% else %}
|
||
<div class="text-center py-8 text-gray-500">
|
||
<span class="text-4xl mb-2 block">✅</span>
|
||
<p>No hay posts pendientes</p>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Scheduled Posts -->
|
||
<div class="card rounded-2xl p-6">
|
||
<div class="flex items-center justify-between mb-6">
|
||
<h2 class="text-lg font-semibold flex items-center gap-2">
|
||
<span>🗓️</span>
|
||
<span>Próximas Publicaciones</span>
|
||
</h2>
|
||
<a href="/calendar" class="text-primary text-sm hover:underline">Ver calendario</a>
|
||
</div>
|
||
<div class="space-y-4">
|
||
{% if scheduled_posts %}
|
||
{% for post in scheduled_posts %}
|
||
<div class="bg-dark-800/50 rounded-xl p-4 hover:bg-dark-700/50 transition-colors">
|
||
<p class="text-sm text-gray-300 line-clamp-2">{{ post.content[:100] }}{% if post.content|length > 100 %}...{% endif %}</p>
|
||
<div class="flex items-center justify-between mt-3">
|
||
<div class="flex items-center gap-2">
|
||
{% for platform in post.platforms %}
|
||
<span class="text-xs bg-dark-700 px-2 py-1 rounded-full">{{ platform }}</span>
|
||
{% endfor %}
|
||
</div>
|
||
<span class="text-xs text-green-400">{{ post.scheduled_at[:16] if post.scheduled_at else '-' }}</span>
|
||
</div>
|
||
</div>
|
||
{% endfor %}
|
||
{% else %}
|
||
<div class="text-center py-8 text-gray-500">
|
||
<span class="text-4xl mb-2 block">📭</span>
|
||
<p>No hay posts programados</p>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Recent Interactions -->
|
||
<div class="card rounded-2xl p-6">
|
||
<div class="flex items-center justify-between mb-6">
|
||
<h2 class="text-lg font-semibold flex items-center gap-2">
|
||
<span>💬</span>
|
||
<span>Interacciones Recientes</span>
|
||
</h2>
|
||
<a href="/interactions" class="text-primary text-sm hover:underline">Ver todas</a>
|
||
</div>
|
||
<div class="space-y-4">
|
||
{% if recent_interactions %}
|
||
{% for interaction in recent_interactions %}
|
||
<div class="bg-dark-800/50 rounded-xl p-4 hover:bg-dark-700/50 transition-colors">
|
||
<div class="flex items-center gap-3 mb-2">
|
||
<div class="w-8 h-8 rounded-full bg-primary/20 flex items-center justify-center text-sm">
|
||
{{ interaction.author_username[0]|upper if interaction.author_username else '?' }}
|
||
</div>
|
||
<div>
|
||
<p class="text-sm font-medium">@{{ interaction.author_username or 'Usuario' }}</p>
|
||
<p class="text-xs text-gray-500">{{ interaction.interaction_type }}</p>
|
||
</div>
|
||
</div>
|
||
<p class="text-sm text-gray-400 line-clamp-2">{{ interaction.content[:80] }}{% if interaction.content|length > 80 %}...{% endif %}</p>
|
||
</div>
|
||
{% endfor %}
|
||
{% else %}
|
||
<div class="text-center py-8 text-gray-500">
|
||
<span class="text-4xl mb-2 block">💤</span>
|
||
<p>No hay interacciones pendientes</p>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Quick Actions -->
|
||
<div class="mt-8">
|
||
<h2 class="text-lg font-semibold mb-4">Acciones Rápidas</h2>
|
||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||
<a href="/compose" class="card rounded-xl p-4 text-center hover:bg-dark-700/50 transition-all group">
|
||
<span class="text-3xl mb-2 block group-hover:scale-110 transition-transform">✍️</span>
|
||
<span class="text-sm text-gray-300">Nuevo Post</span>
|
||
</a>
|
||
<a href="/compose?type=tip" class="card rounded-xl p-4 text-center hover:bg-dark-700/50 transition-all group">
|
||
<span class="text-3xl mb-2 block group-hover:scale-110 transition-transform">💡</span>
|
||
<span class="text-sm text-gray-300">Generar Tip</span>
|
||
</a>
|
||
<a href="/analytics" class="card rounded-xl p-4 text-center hover:bg-dark-700/50 transition-all group">
|
||
<span class="text-3xl mb-2 block group-hover:scale-110 transition-transform">📊</span>
|
||
<span class="text-sm text-gray-300">Ver Analytics</span>
|
||
</a>
|
||
<a href="/settings" class="card rounded-xl p-4 text-center hover:bg-dark-700/50 transition-all group">
|
||
<span class="text-3xl mb-2 block group-hover:scale-110 transition-transform">⚙️</span>
|
||
<span class="text-sm text-gray-300">Configuración</span>
|
||
</a>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Platform Status -->
|
||
<div class="mt-8">
|
||
<h2 class="text-lg font-semibold mb-4">Estado de Plataformas</h2>
|
||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4" id="platform-status">
|
||
<!-- Loaded via JS -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endblock %}
|
||
|
||
{% block extra_scripts %}
|
||
<script>
|
||
async function loadPlatformStatus() {
|
||
const container = document.getElementById('platform-status');
|
||
const platforms = ['x', 'facebook', 'instagram', 'threads'];
|
||
|
||
container.innerHTML = platforms.map(p => `
|
||
<div class="card rounded-xl p-4">
|
||
<div class="flex items-center justify-between">
|
||
<span class="text-xl">${getPlatformIcon(p)}</span>
|
||
<span class="text-xs bg-dark-700 px-2 py-1 rounded-full">Verificando...</span>
|
||
</div>
|
||
<p class="text-sm mt-2 capitalize">${p}</p>
|
||
</div>
|
||
`).join('');
|
||
|
||
try {
|
||
const response = await fetch('/api/publish/test');
|
||
const data = await response.json();
|
||
|
||
container.innerHTML = '';
|
||
for (const [platform, status] of Object.entries(data)) {
|
||
const isConnected = status.configured && status.connected;
|
||
container.innerHTML += `
|
||
<div class="card rounded-xl p-4">
|
||
<div class="flex items-center justify-between">
|
||
<span class="text-xl">${getPlatformIcon(platform)}</span>
|
||
<span class="text-xs ${isConnected ? 'bg-green-500/20 text-green-400' : 'bg-red-500/20 text-red-400'} px-2 py-1 rounded-full">
|
||
${isConnected ? 'Conectado' : 'Desconectado'}
|
||
</span>
|
||
</div>
|
||
<p class="text-sm mt-2 capitalize">${platform}</p>
|
||
${status.details?.username ? `<p class="text-xs text-gray-500">@${status.details.username}</p>` : ''}
|
||
</div>
|
||
`;
|
||
}
|
||
} catch (error) {
|
||
container.innerHTML = '<div class="col-span-4 text-center text-gray-500 py-4">Error cargando estado</div>';
|
||
}
|
||
}
|
||
|
||
function getPlatformIcon(platform) {
|
||
const icons = { x: '𝕏', facebook: '📘', instagram: '📸', threads: '🧵' };
|
||
return icons[platform] || '📱';
|
||
}
|
||
|
||
loadPlatformStatus();
|
||
</script>
|
||
{% endblock %}
|