almeira-web/src/components/NewsTicker.tsx

179 lines
9.0 KiB
TypeScript

import { APPWRITE_ALMEIRA_CONFIG } from '@/lib/appwrite';
import { createAdminClient } from '@/lib/appwrite.server';
import { Query } from 'node-appwrite';
import Link from 'next/link';
import { MessageCircle } from 'lucide-react';
import { Settings } from '@/types/settings';
import LiveClock from './LiveClock';
export default async function NewsTicker() {
let tickerItems: any[] = [];
let showNewsDate = true;
try {
const client: any = await createAdminClient();
if (!client) throw new Error("No Appwrite client available");
// 1. Fetch Settings
const settingsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'settings', []);
const settings = (settingsRes.documents.length > 0 ? settingsRes.documents[0] : {
ticker_count: 10,
rotation_seconds: 5,
feed_news_count: 12,
show_news_date: true
}) as Settings;
showNewsDate = settings.show_news_date !== false;
const tickerCount = settings.ticker_count || 10;
// 2. Fetch News (NOTICIA)
const typeRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'publication_types', [
Query.equal('codigo', 'NOTICIA')
]);
if (typeRes.documents.length > 0) {
const firstType = typeRes.documents[0];
const typeId = firstType?.$id;
if (!typeId) throw new Error("Could not find typeId for NOTICIA");
const postsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'posts', [
Query.equal('tipo_publicacion_id', typeId),
Query.equal('estado', ['Publicado', 'publicado']),
Query.orderDesc('fecha_publicacion'),
Query.limit(tickerCount)
]);
// Resolve Media
const mediaIds = postsRes.documents.map((p: any) => p.imagen_destacada_id).filter((id: any) => id && typeof id === 'string') as string[];
const mediaMap = new Map();
if (mediaIds.length > 0) {
const mediaRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'media', [
Query.equal('$id', mediaIds)
]);
mediaRes.documents.forEach((m: any) => mediaMap.set(m.$id, m));
}
postsRes.documents.forEach((post: any) => {
const media = typeof post.imagen_destacada_id === 'object' ? post.imagen_destacada_id : mediaMap.get(post.imagen_destacada_id);
let imagenUrl = post.og_image_url || null;
if (media) {
imagenUrl = `${APPWRITE_ALMEIRA_CONFIG.endpoint}/storage/buckets/${media.bucket_id}/files/${media.file_id}/view?project=${APPWRITE_ALMEIRA_CONFIG.projectId}`;
}
tickerItems.push({
type: 'news',
id: post.$id,
titulo: post.titulo,
slug: post.slug || post.$id,
imagenUrl
});
});
}
// 3. Fetch Approved WhatsApp Messages
try {
let waRes;
try {
waRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'whatsapp_messages', [
Query.equal('status', 'approved'),
Query.orderDesc('timestamp'),
Query.limit(5)
]);
} catch (queryError: any) {
// El atributo 'status' puede no estar todavía en el esquema de Appwrite (Modo Gracia)
// Usamos fallback de todos los mensajes limitados si falla el filtro
waRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'whatsapp_messages', [
Query.orderDesc('$createdAt'),
Query.limit(5)
]);
}
if (waRes && waRes.documents) {
waRes.documents.forEach((msg: any) => {
tickerItems.push({
type: 'whatsapp',
id: msg.$id,
titulo: `${msg.sender_name || 'Usuario'}: ${msg.content || ''}`,
slug: null,
imagenUrl: msg.avatar || null
});
});
}
} catch (waError) {
console.warn("Skipping WhatsApp messages in ticker (Collection or Schema missing):", waError);
}
} catch (e) {
console.error("Ticker fetch error", e);
}
if (tickerItems.length === 0) return null;
return (
<div className="w-full bg-white dark:bg-slate-900 border-y border-slate-200 dark:border-white/10 h-[44px] flex overflow-hidden z-40 font-sans shadow-sm">
{/* Left Block */}
<div className="z-30 h-full flex items-center bg-cyan-600 text-white px-4 md:px-5 shrink-0 uppercase tracking-widest text-[11px] md:text-[13px] font-black truncate relative">
Últimas Noticias
</div>
{/* Middle Marquee */}
<div className="flex-1 overflow-hidden relative flex items-center h-full bg-white dark:bg-slate-900 z-10">
<div className="absolute left-0 z-20 bg-gradient-to-r from-white dark:from-slate-900 to-transparent w-6 h-full pointer-events-none"></div>
<div className="absolute right-0 z-20 bg-gradient-to-l from-white dark:from-slate-900 to-transparent w-6 h-full pointer-events-none"></div>
<div className="flex animate-marquee whitespace-nowrap items-center hover:[animation-play-state:paused] h-full">
{/* Doble render para bucle infinito visual */}
{[...tickerItems, ...tickerItems].map((item, idx) => {
const isWA = item.type === 'whatsapp';
const Content = (
<div className="flex items-center gap-3 mx-4 md:mx-6 group cursor-pointer border-r border-slate-200 dark:border-slate-800 pr-4 md:pr-6 h-full transition-all duration-300">
{isWA ? (
<div className="w-[42px] h-[28px] rounded bg-green-100 dark:bg-green-500/20 flex items-center justify-center border border-green-200 dark:border-green-500/30 shrink-0">
<MessageCircle size={14} className="text-green-600 dark:text-green-400 fill-current" />
</div>
) : (
item.imagenUrl && (
// eslint-disable-next-line @next/next/no-img-element
<img src={item.imagenUrl} alt={item.titulo} className="w-[42px] h-[28px] object-cover rounded shadow-sm group-hover:brightness-110 transition-all border border-slate-100 dark:border-white/10 shrink-0" />
)
)}
<span className={`text-[13px] font-medium flex items-center gap-2 transition-colors duration-300 ${
isWA ? 'text-green-700 dark:text-green-400 group-hover:text-green-900 dark:group-hover:text-green-300' : 'text-slate-600 dark:text-slate-300 group-hover:text-cyan-600 dark:group-hover:text-cyan-400'
}`}>
{isWA && <span className="text-[10px] font-bold px-1.5 py-0.5 bg-green-100 dark:bg-green-500/20 rounded border border-green-200 dark:border-green-500/30 text-green-700 dark:text-green-400">COMUNIDAD</span>}
{item.titulo.length > 90 ? item.titulo.substring(0, 90) + '...' : item.titulo}
</span>
</div>
);
if (item.slug) {
return <Link key={idx} href={`/${item.slug}`} className="flex items-center h-full">{Content}</Link>;
}
return <div key={idx} className="flex items-center h-full">{Content}</div>;
})}
</div>
</div>
{/* Right Block */}
{showNewsDate && (
<div className="z-30 h-full bg-slate-800 flex items-center px-4 shrink-0 overflow-hidden hidden sm:flex">
<LiveClock />
</div>
)}
<style dangerouslySetInnerHTML={{__html: `
@keyframes marquee {
0% { transform: translateX(0%); }
100% { transform: translateX(-50%); }
}
.animate-marquee {
animation: marquee 40s linear infinite;
}
.animate-marquee:hover {
animation-play-state: paused;
}
`}} />
</div>
)
}