179 lines
9.0 KiB
TypeScript
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>
|
|
)
|
|
}
|