modified: src/app/[slug]/page.tsx
modified: src/app/clasificados/page.tsx modified: src/app/dashboard/[entity]/[id]/page.tsx modified: src/app/dashboard/[entity]/new/page.tsx modified: src/app/dashboard/[entity]/page.tsx modified: src/app/dashboard/entity/page.tsx modified: src/app/dashboard/profile/page.tsx modified: src/app/dashboard/settings/page.tsx modified: src/app/eventos/page.tsx modified: src/app/globals.css modified: src/app/page.tsx modified: src/components/NewsTicker.tsx modified: src/components/ProgrammingSchedule.tsx
This commit is contained in:
parent
80a6f77b74
commit
e3024af88b
@ -35,8 +35,9 @@ async function fetchPostData(slugOrId: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function NoticiaDetail({ params }: { params: { slug: string } }) {
|
export default async function NoticiaDetail({ params }: { params: Promise<{ slug: string }> }) {
|
||||||
const post = await fetchPostData(params.slug);
|
const { slug } = await params;
|
||||||
|
const post = await fetchPostData(slug);
|
||||||
const hasSidebar = await hasSidebarContent();
|
const hasSidebar = await hasSidebarContent();
|
||||||
|
|
||||||
if (!post) {
|
if (!post) {
|
||||||
|
|||||||
@ -13,10 +13,10 @@ async function fetchClasificadosData() {
|
|||||||
const settingsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'settings', []);
|
const settingsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'settings', []);
|
||||||
const settings: any = settingsRes.documents.length > 0 ? settingsRes.documents[0] : {};
|
const settings: any = settingsRes.documents.length > 0 ? settingsRes.documents[0] : {};
|
||||||
|
|
||||||
const postsCount = settings.classifieds_count || 9;
|
const postsCount = settings.feed_news_count || 12;
|
||||||
const cardSettings = {
|
const cardSettings = {
|
||||||
showCategory: settings.news_card_show_category ?? false,
|
showCategory: settings.news_card_show_category ?? false,
|
||||||
showFooterDate: settings.news_card_show_footer_date ?? true,
|
showFooterDate: settings.show_news_date ?? false,
|
||||||
showFooterButton: settings.news_card_show_footer_button ?? true,
|
showFooterButton: settings.news_card_show_footer_button ?? true,
|
||||||
footerText: settings.news_card_footer_text || ""
|
footerText: settings.news_card_footer_text || ""
|
||||||
};
|
};
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import Link from 'next/link';
|
|||||||
import { updateDocumentServer } from '@/app/actions/appwrite';
|
import { updateDocumentServer } from '@/app/actions/appwrite';
|
||||||
import { redirect } from 'next/navigation';
|
import { redirect } from 'next/navigation';
|
||||||
|
|
||||||
export default async function EditEntityPage({ params }: { params: { entity: string, id: string } }) {
|
export default async function EditEntityPage({ params }: { params: Promise<{ entity: string, id: string }> }) {
|
||||||
const { entity, id } = await params;
|
const { entity, id } = await params;
|
||||||
|
|
||||||
const collectionId = collectionMap[entity];
|
const collectionId = collectionMap[entity];
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import Link from 'next/link';
|
|||||||
import { createDocumentServer } from '@/app/actions/appwrite';
|
import { createDocumentServer } from '@/app/actions/appwrite';
|
||||||
import { redirect } from 'next/navigation';
|
import { redirect } from 'next/navigation';
|
||||||
|
|
||||||
export default async function NewEntityPage({ params }: { params: { entity: string } }) {
|
export default async function NewEntityPage({ params }: { params: Promise<{ entity: string }> }) {
|
||||||
const { entity } = await params;
|
const { entity } = await params;
|
||||||
|
|
||||||
const collectionId = collectionMap[entity];
|
const collectionId = collectionMap[entity];
|
||||||
|
|||||||
@ -11,8 +11,8 @@ export default async function EntityPage({
|
|||||||
params,
|
params,
|
||||||
searchParams
|
searchParams
|
||||||
}: {
|
}: {
|
||||||
params: { entity: string },
|
params: Promise<{ entity: string }>,
|
||||||
searchParams: { q?: string, page?: string }
|
searchParams: Promise<{ q?: string, page?: string }>
|
||||||
}) {
|
}) {
|
||||||
const { entity } = await params;
|
const { entity } = await params;
|
||||||
const sParams = await searchParams;
|
const sParams = await searchParams;
|
||||||
|
|||||||
@ -1,13 +1,26 @@
|
|||||||
import { getEntity, updateEntity } from '@/app/actions/entity';
|
import { getEntity, updateEntity } from '@/app/actions/entity';
|
||||||
import { Building, Image as ImageIcon, Link as LinkIcon, Share2, Tv, RadioTower } from 'lucide-react';
|
import { Building, Image as ImageIcon, Link as LinkIcon, Share2, Tv, RadioTower, Facebook, Instagram, Twitter, Youtube, Send, Mail, Globe, MapPin, Phone, MessageSquare } from 'lucide-react';
|
||||||
|
import { createSessionClient } from '@/lib/appwrite.server';
|
||||||
|
import { APPWRITE_ALMEIRA_CONFIG } from '@/lib/appwrite';
|
||||||
|
|
||||||
export default async function EntityPage() {
|
export default async function EntityPage() {
|
||||||
let entity: any = null;
|
let entity: any = null;
|
||||||
|
let cities: any[] = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
entity = await getEntity();
|
entity = await getEntity();
|
||||||
|
|
||||||
|
const sessionClient = await createSessionClient();
|
||||||
|
if (sessionClient) {
|
||||||
|
const citiesRes = await sessionClient.databases.listDocuments(
|
||||||
|
APPWRITE_ALMEIRA_CONFIG.databaseId,
|
||||||
|
'cities',
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
cities = citiesRes.documents;
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("No se pudo cargar config de entidad:", e);
|
console.error("No se pudo cargar la información de la entidad o ciudades:", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateEntityClient(formData: FormData) {
|
async function updateEntityClient(formData: FormData) {
|
||||||
@ -17,7 +30,7 @@ export default async function EntityPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-5xl mx-auto space-y-6 animate-fade-in pb-12">
|
<div className="max-w-5xl mx-auto space-y-6 animate-fade-in pb-12">
|
||||||
<div className="flex items-center gap-4 bg-slate-900 border border-slate-800 p-6 rounded-2xl shadow-xl">
|
<div className="flex items-center gap-4 bg-slate-900 border border-slate-800 p-6 rounded-2xl shadow-xl shadow-cyan-500/5">
|
||||||
<div className="p-3 bg-blue-500/10 rounded-xl text-blue-400">
|
<div className="p-3 bg-blue-500/10 rounded-xl text-blue-400">
|
||||||
<Building size={32} />
|
<Building size={32} />
|
||||||
</div>
|
</div>
|
||||||
@ -25,7 +38,7 @@ export default async function EntityPage() {
|
|||||||
<h1 className="text-3xl font-black font-outfit uppercase tracking-tighter text-white">
|
<h1 className="text-3xl font-black font-outfit uppercase tracking-tighter text-white">
|
||||||
Perfil de Entidad
|
Perfil de Entidad
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-sm font-medium text-slate-400">Identidad Corporativa, Contacto y Canales de Streaming</p>
|
<p className="text-sm font-medium text-slate-400 font-bold uppercase tracking-wider">Identidad Corporativa, Contacto y Canales</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -36,7 +49,7 @@ export default async function EntityPage() {
|
|||||||
<h3 className="text-lg font-bold text-white flex items-center gap-2 border-b border-slate-800 pb-2"><ImageIcon size={18} className="text-cyan-400" /> Identidad Visual</h3>
|
<h3 className="text-lg font-bold text-white flex items-center gap-2 border-b border-slate-800 pb-2"><ImageIcon size={18} className="text-cyan-400" /> Identidad Visual</h3>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="text-xs font-bold tracking-widest text-slate-300 uppercase block mb-2">Razón Social / Nombre Entidad *</label>
|
<label className="text-[10px] font-black tracking-widest text-slate-500 uppercase block mb-2">Razón Social / Nombre Entidad *</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="razon_social"
|
name="razon_social"
|
||||||
@ -47,7 +60,7 @@ export default async function EntityPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="text-xs font-bold tracking-widest text-slate-300 uppercase block mb-2">Sube el Logotipo (Dejar vacío para conservar el actual)</label>
|
<label className="text-[10px] font-black tracking-widest text-slate-500 uppercase block mb-2">Logotipo Corporativo</label>
|
||||||
{entity?.logotipo && (
|
{entity?.logotipo && (
|
||||||
<div className="mb-3 p-3 bg-slate-950 rounded-xl inline-block border border-slate-800">
|
<div className="mb-3 p-3 bg-slate-950 rounded-xl inline-block border border-slate-800">
|
||||||
<img src={entity.logotipo} alt="Logo actual" className="h-16 object-contain" />
|
<img src={entity.logotipo} alt="Logo actual" className="h-16 object-contain" />
|
||||||
@ -57,7 +70,7 @@ export default async function EntityPage() {
|
|||||||
type="file"
|
type="file"
|
||||||
name="logotipo"
|
name="logotipo"
|
||||||
accept="image/*"
|
accept="image/*"
|
||||||
className="w-full bg-slate-950/50 border border-slate-800 rounded-xl file:mr-4 file:py-3 file:px-4 file:rounded-l-xl file:border-0 file:text-sm file:font-semibold file:bg-cyan-500/10 file:text-cyan-400 hover:file:bg-cyan-500/20 text-slate-300 text-sm transition-all"
|
className="w-full bg-slate-950/50 border border-slate-800 rounded-xl file:mr-4 file:py-3 file:px-4 file:rounded-l-xl file:border-0 file:text-[10px] file:font-black file:uppercase file:bg-cyan-500/10 file:text-cyan-400 hover:file:bg-cyan-500/20 text-slate-300 text-sm transition-all"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -71,36 +84,55 @@ export default async function EntityPage() {
|
|||||||
|
|
||||||
{/* Contacto Físico */}
|
{/* Contacto Físico */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<h3 className="text-lg font-bold text-white flex items-center gap-2 border-b border-slate-800 pb-2"><LinkIcon size={18} className="text-green-400" /> Contacto Oficial</h3>
|
<h3 className="text-lg font-bold text-white flex items-center gap-2 border-b border-slate-800 pb-2"><MapPin size={18} className="text-green-400" /> Contacto Oficial</h3>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="text-xs font-bold tracking-widest text-slate-300 uppercase block mb-2">Ciudad</label>
|
<label className="text-[10px] font-black tracking-widest text-slate-500 uppercase block mb-2">Ciudad</label>
|
||||||
<input type="text" name="ciudad" defaultValue={entity?.ciudad || ''} className="w-full bg-slate-950 border border-slate-800 rounded-xl px-4 py-2 text-white focus:border-cyan-500 transition-all" />
|
<select
|
||||||
|
name="ciudad"
|
||||||
|
defaultValue={entity?.ciudad || ''}
|
||||||
|
className="w-full bg-slate-950 border border-slate-800 rounded-xl px-4 py-2 text-white focus:border-cyan-500 transition-all"
|
||||||
|
>
|
||||||
|
<option value="">Seleccionar Ciudad...</option>
|
||||||
|
{cities.map(city => (
|
||||||
|
<option key={city.$id} value={city.descripcion}>{city.descripcion}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="text-xs font-bold tracking-widest text-slate-300 uppercase block mb-2">Dirección Físico</label>
|
<label className="text-[10px] font-black tracking-widest text-slate-500 uppercase block mb-2">Dirección Física</label>
|
||||||
<input type="text" name="direccion" defaultValue={entity?.direccion || ''} className="w-full bg-slate-950 border border-slate-800 rounded-xl px-4 py-2 text-white focus:border-cyan-500 transition-all" />
|
<input type="text" name="direccion" defaultValue={entity?.direccion || ''} className="w-full bg-slate-950 border border-slate-800 rounded-xl px-4 py-2 text-white focus:border-cyan-500 transition-all" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="text-xs font-bold tracking-widest text-slate-300 uppercase block mb-2">Teléfono</label>
|
<label className="text-[10px] font-black tracking-widest text-slate-500 uppercase block mb-2">Teléfono</label>
|
||||||
<input type="text" name="numero_contacto" defaultValue={entity?.numero_contacto || ''} className="w-full bg-slate-950 border border-slate-800 rounded-xl px-4 py-2 text-white focus:border-cyan-500 transition-all" />
|
<input type="text" name="numero_contacto" defaultValue={entity?.numero_contacto || ''} className="w-full bg-slate-950 border border-slate-800 rounded-xl px-4 py-2 text-white focus:border-cyan-500 transition-all" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="text-xs font-bold tracking-widest text-slate-300 uppercase block mb-2">WhatsApp</label>
|
<label className="text-[10px] font-black tracking-widest text-slate-500 uppercase block mb-2">WhatsApp</label>
|
||||||
<input type="text" name="numero_whatsapp" defaultValue={entity?.numero_whatsapp || ''} className="w-full bg-slate-950 border border-slate-800 rounded-xl px-4 py-2 text-white focus:border-cyan-500 transition-all" />
|
<input type="text" name="numero_whatsapp" defaultValue={entity?.numero_whatsapp || ''} className="w-full bg-slate-950 border border-slate-800 rounded-xl px-4 py-2 text-white focus:border-cyan-500 transition-all" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-2 gap-4">
|
|
||||||
|
{/* Email y Sitio Web en líneas separadas como solicitó el usuario */}
|
||||||
<div>
|
<div>
|
||||||
<label className="text-xs font-bold tracking-widest text-slate-300 uppercase block mb-2">Email</label>
|
<label className="text-[10px] font-black tracking-widest text-slate-500 uppercase block mb-2">Email Corporativo</label>
|
||||||
<input type="email" name="email" defaultValue={entity?.email || ''} className="w-full bg-slate-950 border border-slate-800 rounded-xl px-4 py-2 text-white focus:border-cyan-500 transition-all" />
|
<div className="relative group">
|
||||||
|
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-slate-600 group-focus-within:text-cyan-500 transition-colors">
|
||||||
|
<Mail size={18} />
|
||||||
|
</div>
|
||||||
|
<input type="email" name="email" defaultValue={entity?.email || ''} className="w-full bg-slate-950 border border-slate-800 rounded-xl pl-10 pr-4 py-3 text-white focus:border-cyan-500 transition-all font-medium" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="text-xs font-bold tracking-widest text-slate-300 uppercase block mb-2">Sitio Web URL</label>
|
<label className="text-[10px] font-black tracking-widest text-slate-500 uppercase block mb-2">Sitio Web URL</label>
|
||||||
<input type="url" name="sitio_web" defaultValue={entity?.sitio_web || ''} className="w-full bg-slate-950 border border-slate-800 rounded-xl px-4 py-2 text-white focus:border-cyan-500 transition-all" />
|
<div className="relative group">
|
||||||
|
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-slate-600 group-focus-within:text-cyan-500 transition-colors">
|
||||||
|
<Globe size={18} />
|
||||||
|
</div>
|
||||||
|
<input type="url" name="sitio_web" defaultValue={entity?.sitio_web || ''} className="w-full bg-slate-950 border border-slate-800 rounded-xl pl-10 pr-4 py-3 text-white focus:border-cyan-500 transition-all font-medium" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -111,7 +143,7 @@ export default async function EntityPage() {
|
|||||||
<h3 className="text-lg font-bold text-white flex items-center gap-2 border-b border-slate-800 pb-2"><Tv size={18} className="text-rose-400" /> Canales en Vivo (Streaming)</h3>
|
<h3 className="text-lg font-bold text-white flex items-center gap-2 border-b border-slate-800 pb-2"><Tv size={18} className="text-rose-400" /> Canales en Vivo (Streaming)</h3>
|
||||||
<div className="grid md:grid-cols-2 gap-8">
|
<div className="grid md:grid-cols-2 gap-8">
|
||||||
<div>
|
<div>
|
||||||
<label className="text-xs font-bold tracking-widest text-slate-300 uppercase block mb-2 text-cyan-400">URL Streaming TV (YouTube/HLS/iFrame Source)</label>
|
<label className="text-[10px] font-black tracking-widest text-slate-500 uppercase block mb-2 text-cyan-400">URL Streaming TV</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="tv_url"
|
name="tv_url"
|
||||||
@ -119,10 +151,9 @@ export default async function EntityPage() {
|
|||||||
placeholder="https://www.youtube.com/embed/..."
|
placeholder="https://www.youtube.com/embed/..."
|
||||||
className="w-full bg-slate-950 border border-slate-800 rounded-xl px-4 py-3 text-white focus:border-cyan-500 transition-all font-mono text-sm"
|
className="w-full bg-slate-950 border border-slate-800 rounded-xl px-4 py-3 text-white focus:border-cyan-500 transition-all font-mono text-sm"
|
||||||
/>
|
/>
|
||||||
<p className="text-[10px] text-slate-500 mt-2">Esta URL se usará para el reproductor principal de la sección TV.</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="text-xs font-bold tracking-widest text-slate-300 uppercase block mb-2 text-rose-400">URL Streaming Radio (Audio Stream / Icecast)</label>
|
<label className="text-[10px] font-black tracking-widest text-slate-500 uppercase block mb-2 text-rose-400">URL Streaming Radio (Audio Stream)</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="radio_url"
|
name="radio_url"
|
||||||
@ -130,43 +161,52 @@ export default async function EntityPage() {
|
|||||||
placeholder="https://stream.server.com:8000/live"
|
placeholder="https://stream.server.com:8000/live"
|
||||||
className="w-full bg-slate-950 border border-slate-800 rounded-xl px-4 py-3 text-white focus:border-rose-500 transition-all font-mono text-sm"
|
className="w-full bg-slate-950 border border-slate-800 rounded-xl px-4 py-3 text-white focus:border-rose-500 transition-all font-mono text-sm"
|
||||||
/>
|
/>
|
||||||
<p className="text-[10px] text-slate-500 mt-2">Enlace directo al flujo de audio para la sección Radio.</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Redes Sociales */}
|
{/* Redes Sociales con Iconos */}
|
||||||
<div className="space-y-6 pt-4 border-t border-slate-800">
|
<div className="space-y-6 pt-4 border-t border-slate-800">
|
||||||
<h3 className="text-lg font-bold text-white flex items-center gap-2 border-b border-slate-800 pb-2"><Share2 size={18} className="text-purple-400" /> Vínculos Sociales</h3>
|
<h3 className="text-lg font-bold text-white flex items-center gap-2 border-b border-slate-800 pb-2"><Share2 size={18} className="text-purple-400" /> Vínculos Sociales</h3>
|
||||||
<div className="grid md:grid-cols-5 gap-4">
|
<div className="grid md:grid-cols-5 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="text-xs font-bold tracking-widest text-slate-300 uppercase block mb-2 text-blue-500">Facebook</label>
|
<label className="text-[10px] font-black tracking-widest text-slate-500 uppercase flex items-center gap-2 mb-2 text-blue-500">
|
||||||
|
<Facebook size={12} /> Facebook
|
||||||
|
</label>
|
||||||
<input type="url" name="facebook_url" defaultValue={entity?.facebook_url || ''} className="w-full bg-slate-950 border border-slate-800 rounded-xl px-4 py-2.5 text-white focus:border-blue-500 text-sm transition-all" placeholder="https://..." />
|
<input type="url" name="facebook_url" defaultValue={entity?.facebook_url || ''} className="w-full bg-slate-950 border border-slate-800 rounded-xl px-4 py-2.5 text-white focus:border-blue-500 text-sm transition-all" placeholder="https://..." />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="text-xs font-bold tracking-widest text-slate-300 uppercase block mb-2 text-pink-500">Instagram</label>
|
<label className="text-[10px] font-black tracking-widest text-slate-500 uppercase flex items-center gap-2 mb-2 text-pink-500">
|
||||||
|
<Instagram size={12} /> Instagram
|
||||||
|
</label>
|
||||||
<input type="url" name="instagram_url" defaultValue={entity?.instagram_url || ''} className="w-full bg-slate-950 border border-slate-800 rounded-xl px-4 py-2.5 text-white focus:border-pink-500 text-sm transition-all" placeholder="https://..." />
|
<input type="url" name="instagram_url" defaultValue={entity?.instagram_url || ''} className="w-full bg-slate-950 border border-slate-800 rounded-xl px-4 py-2.5 text-white focus:border-pink-500 text-sm transition-all" placeholder="https://..." />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="text-xs font-bold tracking-widest text-slate-300 uppercase block mb-2 text-slate-400">X (Twitter)</label>
|
<label className="text-[10px] font-black tracking-widest text-slate-500 uppercase flex items-center gap-2 mb-2 text-slate-200">
|
||||||
|
<Twitter size={12} /> X (Twitter)
|
||||||
|
</label>
|
||||||
<input type="url" name="x_url" defaultValue={entity?.x_url || ''} className="w-full bg-slate-950 border border-slate-800 rounded-xl px-4 py-2.5 text-white focus:border-slate-500 text-sm transition-all" placeholder="https://..." />
|
<input type="url" name="x_url" defaultValue={entity?.x_url || ''} className="w-full bg-slate-950 border border-slate-800 rounded-xl px-4 py-2.5 text-white focus:border-slate-500 text-sm transition-all" placeholder="https://..." />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="text-xs font-bold tracking-widest text-slate-300 uppercase block mb-2 text-red-500">YouTube</label>
|
<label className="text-[10px] font-black tracking-widest text-slate-500 uppercase flex items-center gap-2 mb-2 text-red-500">
|
||||||
|
<Youtube size={12} /> YouTube
|
||||||
|
</label>
|
||||||
<input type="url" name="youtube_url" defaultValue={entity?.youtube_url || ''} className="w-full bg-slate-950 border border-slate-800 rounded-xl px-4 py-2.5 text-white focus:border-red-500 text-sm transition-all" placeholder="https://..." />
|
<input type="url" name="youtube_url" defaultValue={entity?.youtube_url || ''} className="w-full bg-slate-950 border border-slate-800 rounded-xl px-4 py-2.5 text-white focus:border-red-500 text-sm transition-all" placeholder="https://..." />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="text-xs font-bold tracking-widest text-slate-300 uppercase block mb-2 text-cyan-500">Telegram</label>
|
<label className="text-[10px] font-black tracking-widest text-slate-500 uppercase flex items-center gap-2 mb-2 text-cyan-500">
|
||||||
|
<Send size={12} /> Telegram
|
||||||
|
</label>
|
||||||
<input type="url" name="telegram_url" defaultValue={entity?.telegram_url || ''} className="w-full bg-slate-950 border border-slate-800 rounded-xl px-4 py-2.5 text-white focus:border-cyan-500 text-sm transition-all" placeholder="https://..." />
|
<input type="url" name="telegram_url" defaultValue={entity?.telegram_url || ''} className="w-full bg-slate-950 border border-slate-800 rounded-xl px-4 py-2.5 text-white focus:border-cyan-500 text-sm transition-all" placeholder="https://..." />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="pt-6 border-t border-slate-800">
|
<div className="pt-6 border-t border-slate-800">
|
||||||
<button type="submit" className="w-full md:w-auto relative flex items-center gap-2 justify-center group overflow-hidden bg-slate-800 text-white font-black uppercase tracking-widest py-3 px-8 rounded-xl transition-all border border-blue-500/50 hover:border-blue-400 ml-auto shadow-[0_0_20px_rgba(59,130,246,0.3)]">
|
<button type="submit" className="w-full md:w-auto relative flex items-center gap-2 justify-center group overflow-hidden bg-slate-800 text-white font-black uppercase tracking-widest py-3 px-12 rounded-xl transition-all border border-blue-500/50 hover:border-blue-400 ml-auto shadow-[0_0_20px_rgba(59,130,246,0.3)]">
|
||||||
<div className="absolute inset-0 bg-gradient-to-r from-blue-600 to-indigo-500 opacity-90 group-hover:opacity-100 transition-opacity"></div>
|
<div className="absolute inset-0 bg-gradient-to-r from-blue-600 to-indigo-500 opacity-90 group-hover:opacity-100 transition-opacity"></div>
|
||||||
<Building size={18} className="relative z-10" />
|
<Building size={18} className="relative z-10" />
|
||||||
<span className="relative z-10 text-sm">Guardar Perfil de Entidad</span>
|
<span className="relative z-10 text-sm">Guardar</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@ -140,7 +140,7 @@ export default function ProfilePage() {
|
|||||||
<button disabled={loading} type="submit" className="w-full relative group overflow-hidden bg-slate-800 text-white font-black uppercase tracking-widest py-4 px-6 rounded-xl transition-all border border-cyan-500/50 hover:border-cyan-400 shadow-[0_0_20px_rgba(6,182,212,0.1)]">
|
<button disabled={loading} type="submit" className="w-full relative group overflow-hidden bg-slate-800 text-white font-black uppercase tracking-widest py-4 px-6 rounded-xl transition-all border border-cyan-500/50 hover:border-cyan-400 shadow-[0_0_20px_rgba(6,182,212,0.1)]">
|
||||||
<div className="absolute inset-0 bg-gradient-to-r from-cyan-600 to-blue-600 opacity-80 group-hover:opacity-100 transition-opacity"></div>
|
<div className="absolute inset-0 bg-gradient-to-r from-cyan-600 to-blue-600 opacity-80 group-hover:opacity-100 transition-opacity"></div>
|
||||||
<span className="relative z-10 flex items-center justify-center gap-2">
|
<span className="relative z-10 flex items-center justify-center gap-2">
|
||||||
{loading ? <Loader className="animate-spin" size={18} /> : <>Guardar Cambios <ArrowRight size={18} /></>}
|
{loading ? <Loader className="animate-spin" size={18} /> : <>Guardar <ArrowRight size={18} /></>}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@ -35,15 +35,26 @@ export default async function SettingsPage() {
|
|||||||
|
|
||||||
const updateData = {
|
const updateData = {
|
||||||
ticker_count: parseInt(formData.get('ticker_count') as string),
|
ticker_count: parseInt(formData.get('ticker_count') as string),
|
||||||
rotation_seconds: parseInt(formData.get('rotation_seconds') as string)
|
feed_news_count: parseInt(formData.get('feed_news_count') as string),
|
||||||
|
rotation_seconds: parseInt(formData.get('rotation_seconds') as string),
|
||||||
|
show_news_date: formData.get('show_news_date') === 'true'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (formData.get('docId')) {
|
||||||
await sessionClient.databases.updateDocument(
|
await sessionClient.databases.updateDocument(
|
||||||
APPWRITE_ALMEIRA_CONFIG.databaseId,
|
APPWRITE_ALMEIRA_CONFIG.databaseId,
|
||||||
'settings',
|
'settings',
|
||||||
formData.get('docId') as string,
|
formData.get('docId') as string,
|
||||||
updateData
|
updateData
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
await sessionClient.databases.createDocument(
|
||||||
|
APPWRITE_ALMEIRA_CONFIG.databaseId,
|
||||||
|
'settings',
|
||||||
|
'unique()',
|
||||||
|
updateData
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
revalidatePath('/dashboard/settings');
|
revalidatePath('/dashboard/settings');
|
||||||
revalidatePath('/'); // Revalidate frontpage
|
revalidatePath('/'); // Revalidate frontpage
|
||||||
@ -56,7 +67,7 @@ export default async function SettingsPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-5xl mx-auto space-y-6 animate-fade-in pb-12">
|
<div className="max-w-5xl mx-auto space-y-6 animate-fade-in pb-12">
|
||||||
<div className="flex items-center gap-4 bg-slate-900 border border-slate-800 p-6 rounded-2xl shadow-xl">
|
<div className="flex items-center gap-4 bg-slate-900 border border-slate-800 p-6 rounded-2xl shadow-xl shadow-cyan-500/5">
|
||||||
<div className="p-3 bg-cyan-500/10 rounded-xl text-cyan-400">
|
<div className="p-3 bg-cyan-500/10 rounded-xl text-cyan-400">
|
||||||
<Settings size={32} />
|
<Settings size={32} />
|
||||||
</div>
|
</div>
|
||||||
@ -75,58 +86,83 @@ export default async function SettingsPage() {
|
|||||||
<input type="hidden" name="docId" value={docId} />
|
<input type="hidden" name="docId" value={docId} />
|
||||||
|
|
||||||
{/* Configurador del Ticker */}
|
{/* Configurador del Ticker */}
|
||||||
<div className="space-y-4">
|
<div className="space-y-6">
|
||||||
<div className="flex items-center gap-2 mb-4">
|
<div className="flex items-center gap-2 border-b border-slate-800 pb-2">
|
||||||
<Menu className="text-pink-500" />
|
<Menu className="text-cyan-500" size={20} />
|
||||||
<h2 className="text-xl font-bold uppercase tracking-widest text-white">Ticker de Noticias</h2>
|
<h2 className="text-lg font-bold uppercase tracking-widest text-white">Noticias y Feed</h2>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-slate-400 text-sm h-12">
|
|
||||||
Gobierna el cintillo de 'Últimas Noticias' que rota bajo el encabezado superior estableciendo su límite histórico.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="text-xs font-bold tracking-widest text-slate-300 uppercase block mb-2">Cantidad a Mostrar</label>
|
<label className="text-[10px] font-black tracking-widest text-slate-500 uppercase block mb-2">Ticker (Cintillo)</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
name="ticker_count"
|
name="ticker_count"
|
||||||
defaultValue={settings.ticker_count}
|
defaultValue={settings.ticker_count || 10}
|
||||||
min="1" max="50"
|
min="1" max="50"
|
||||||
required
|
required
|
||||||
className="w-full bg-slate-950 border border-slate-800 rounded-xl px-4 py-3 text-white focus:border-cyan-500 text-xl font-black font-mono transition-all"
|
className="w-full bg-slate-950 border border-slate-800 rounded-xl px-4 py-3 text-white focus:border-cyan-500 text-xl font-black font-mono transition-all"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="text-[10px] font-black tracking-widest text-slate-500 uppercase block mb-2">Feed (Inicio)</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
name="feed_news_count"
|
||||||
|
defaultValue={(settings as any).feed_news_count || 12}
|
||||||
|
min="4" max="100"
|
||||||
|
required
|
||||||
|
className="w-full bg-slate-950 border border-slate-800 rounded-xl px-4 py-3 text-white focus:border-cyan-400 text-xl font-black font-mono transition-all"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-3 bg-slate-950 p-4 border border-slate-800 rounded-xl hover:border-cyan-500/30 transition-colors">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
name="show_news_date"
|
||||||
|
value="true"
|
||||||
|
defaultChecked={(settings as any).show_news_date === true}
|
||||||
|
id="show_news_date"
|
||||||
|
className="w-5 h-5 accent-cyan-500 cursor-pointer"
|
||||||
|
/>
|
||||||
|
<label htmlFor="show_news_date" className="text-sm font-bold text-slate-300 cursor-pointer select-none">
|
||||||
|
Mostrar fecha de publicación en las tarjetas del feed
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Configurador de Rotación (Banners) */}
|
{/* Configurador de Rotación (Banners) */}
|
||||||
<div className="space-y-4">
|
<div className="space-y-6">
|
||||||
<div className="flex items-center gap-2 mb-4">
|
<div className="flex items-center gap-2 border-b border-slate-800 pb-2">
|
||||||
<Clock className="text-purple-500" />
|
<Clock className="text-purple-500" size={20} />
|
||||||
<h2 className="text-xl font-bold uppercase tracking-widest text-white">Velocidad Carrusel</h2>
|
<h2 className="text-lg font-bold uppercase tracking-widest text-white">Visualización</h2>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-slate-400 text-sm h-12">
|
|
||||||
Regula el tiempo en pantalla antes de que cambie la lámina (Afecta Banner Principal y Sliders Patrocinados).
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<label className="text-xs font-bold tracking-widest text-slate-300 uppercase block mb-2">Segundos en Pantalla</label>
|
<label className="text-[10px] font-black tracking-widest text-slate-500 uppercase block mb-2">Segundos en Pantalla (Banners)</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
name="rotation_seconds"
|
name="rotation_seconds"
|
||||||
defaultValue={settings.rotation_seconds}
|
defaultValue={settings.rotation_seconds || 5}
|
||||||
min="1" max="60"
|
min="1" max="60"
|
||||||
required
|
required
|
||||||
className="w-full bg-slate-950 border border-slate-800 rounded-xl px-4 py-3 text-white focus:border-purple-500 text-xl font-black font-mono transition-all"
|
className="w-full bg-slate-950 border border-slate-800 rounded-xl px-4 py-3 text-white focus:border-purple-500 text-xl font-black font-mono transition-all"
|
||||||
/>
|
/>
|
||||||
<span className="absolute right-4 bottom-3 text-slate-500 font-bold tracking-widest uppercase text-sm">Segundos</span>
|
<span className="absolute right-4 bottom-3 text-slate-600 font-bold tracking-widest uppercase text-[10px]">Segundos</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<p className="text-[11px] text-slate-500 italic leading-relaxed">
|
||||||
|
Estas configuraciones afectan el comportamiento dinámico del sitio web principal y los tiempos de carga de recursos del lado del servidor.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="pt-6 border-t border-slate-800">
|
<div className="pt-6 border-t border-slate-800">
|
||||||
<button type="submit" className="w-full md:w-auto relative flex items-center gap-2 justify-center group overflow-hidden bg-slate-800 text-white font-black uppercase tracking-widest py-3 px-8 rounded-xl transition-all border border-cyan-500/50 hover:border-cyan-400 ml-auto">
|
<button type="submit" className="w-full md:w-auto relative flex items-center gap-2 justify-center group overflow-hidden bg-slate-800 text-white font-black uppercase tracking-widest py-3 px-10 rounded-xl transition-all border border-cyan-500/50 hover:border-cyan-400 ml-auto shadow-[0_0_20px_rgba(6,182,212,0.2)]">
|
||||||
<div className="absolute inset-0 bg-gradient-to-r from-cyan-600 to-blue-500 opacity-90 group-hover:opacity-100 transition-opacity"></div>
|
<div className="absolute inset-0 bg-gradient-to-r from-cyan-600 to-blue-500 opacity-90 group-hover:opacity-100 transition-opacity"></div>
|
||||||
<Save size={18} className="relative z-10" />
|
<Save size={18} className="relative z-10" />
|
||||||
<span className="relative z-10 text-sm">Guardar Motor Web</span>
|
<span className="relative z-10 text-sm">Guardar</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@ -13,10 +13,10 @@ async function fetchEventosData() {
|
|||||||
const settingsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'settings', []);
|
const settingsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'settings', []);
|
||||||
const settings: any = settingsRes.documents.length > 0 ? settingsRes.documents[0] : {};
|
const settings: any = settingsRes.documents.length > 0 ? settingsRes.documents[0] : {};
|
||||||
|
|
||||||
const postsCount = settings.events_count || 9;
|
const postsCount = settings.feed_news_count || 12;
|
||||||
const cardSettings = {
|
const cardSettings = {
|
||||||
showCategory: settings.news_card_show_category ?? false,
|
showCategory: settings.news_card_show_category ?? false,
|
||||||
showFooterDate: settings.news_card_show_footer_date ?? true,
|
showFooterDate: settings.show_news_date ?? false,
|
||||||
showFooterButton: settings.news_card_show_footer_button ?? true,
|
showFooterButton: settings.news_card_show_footer_button ?? true,
|
||||||
footerText: settings.news_card_footer_text || ""
|
footerText: settings.news_card_footer_text || ""
|
||||||
};
|
};
|
||||||
|
|||||||
@ -86,14 +86,17 @@
|
|||||||
/* Scrollbar futurista */
|
/* Scrollbar futurista */
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 8px;
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
}
|
}
|
||||||
::-webkit-scrollbar-track {
|
::-webkit-scrollbar-track {
|
||||||
background: #020617;
|
background: #020617;
|
||||||
}
|
}
|
||||||
::-webkit-scrollbar-thumb {
|
::-webkit-scrollbar-thumb {
|
||||||
background: #1e293b;
|
background: #06b6d4; /* Cyan */
|
||||||
border-radius: 4px;
|
background: linear-gradient(180deg, #06b6d4 0%, #3b82f6 100%);
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 2px solid #020617;
|
||||||
}
|
}
|
||||||
::-webkit-scrollbar-thumb:hover {
|
::-webkit-scrollbar-thumb:hover {
|
||||||
background: #334155;
|
background: #22d3ee;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,10 +13,10 @@ async function fetchHomeData() {
|
|||||||
const settingsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'settings', []);
|
const settingsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'settings', []);
|
||||||
const settings: any = settingsRes.documents.length > 0 ? settingsRes.documents[0] : {};
|
const settings: any = settingsRes.documents.length > 0 ? settingsRes.documents[0] : {};
|
||||||
|
|
||||||
const postsCount = settings.feed_count || settings.post_count || 9;
|
const postsCount = settings.feed_news_count || 12;
|
||||||
const cardSettings = {
|
const cardSettings = {
|
||||||
showCategory: settings.news_card_show_category ?? false,
|
showCategory: settings.news_card_show_category ?? false,
|
||||||
showFooterDate: settings.news_card_show_footer_date ?? true,
|
showFooterDate: settings.show_news_date ?? false,
|
||||||
showFooterButton: settings.news_card_show_footer_button ?? true,
|
showFooterButton: settings.news_card_show_footer_button ?? true,
|
||||||
footerText: settings.news_card_footer_text || ""
|
footerText: settings.news_card_footer_text || ""
|
||||||
};
|
};
|
||||||
|
|||||||
@ -42,16 +42,16 @@ export default async function NewsTicker() {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
// Resolve Media
|
// Resolve Media
|
||||||
const mediaIds = postsRes.documents.map(p => p.imagen_destacada_id).filter(id => id && typeof id === 'string');
|
const mediaIds = postsRes.documents.map((p: any) => p.imagen_destacada_id).filter((id: any) => id && typeof id === 'string');
|
||||||
const mediaMap = new Map();
|
const mediaMap = new Map();
|
||||||
if (mediaIds.length > 0) {
|
if (mediaIds.length > 0) {
|
||||||
const mediaRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'media', [
|
const mediaRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'media', [
|
||||||
Query.equal('$id', mediaIds)
|
Query.equal('$id', mediaIds)
|
||||||
]);
|
]);
|
||||||
mediaRes.documents.forEach(m => mediaMap.set(m.$id, m));
|
mediaRes.documents.forEach((m: any) => mediaMap.set(m.$id, m));
|
||||||
}
|
}
|
||||||
|
|
||||||
newsItems = postsRes.documents.map(post => {
|
newsItems = postsRes.documents.map((post: any) => {
|
||||||
const media = mediaMap.get(post.imagen_destacada_id);
|
const media = mediaMap.get(post.imagen_destacada_id);
|
||||||
let imagenUrl = post.og_image_url || null;
|
let imagenUrl = post.og_image_url || null;
|
||||||
if (media) {
|
if (media) {
|
||||||
@ -92,7 +92,7 @@ export default async function NewsTicker() {
|
|||||||
>
|
>
|
||||||
{noticia.imagenUrl && (
|
{noticia.imagenUrl && (
|
||||||
// eslint-disable-next-line @next/next/no-img-element
|
// eslint-disable-next-line @next/next/no-img-element
|
||||||
<img src={noticia.imagenUrl} alt={noticia.titulo} className="w-10 h-10 object-cover rounded-lg shadow-[0_0_10px_rgba(6,182,212,0.3)] group-hover:scale-110 group-hover:shadow-[0_0_15px_rgba(6,182,212,0.5)] transition-all duration-300 border border-white/10" />
|
<img src={noticia.imagenUrl} alt={noticia.titulo} className="w-16 h-9 aspect-video object-cover rounded shadow-[0_0_10px_rgba(6,182,212,0.3)] group-hover:scale-105 group-hover:shadow-[0_0_15px_rgba(6,182,212,0.5)] transition-all duration-300 border border-white/10" />
|
||||||
)}
|
)}
|
||||||
<span className="text-sm font-bold text-slate-300 group-hover:text-cyan-400 transition-colors uppercase tracking-widest flex items-center gap-2">
|
<span className="text-sm font-bold text-slate-300 group-hover:text-cyan-400 transition-colors uppercase tracking-widest flex items-center gap-2">
|
||||||
{!noticia.imagenUrl && <span className="w-2 h-2 rounded-full bg-rose-500 animate-pulse"></span>}
|
{!noticia.imagenUrl && <span className="w-2 h-2 rounded-full bg-rose-500 animate-pulse"></span>}
|
||||||
|
|||||||
@ -4,15 +4,16 @@ import { useEffect, useState, useRef } from 'react';
|
|||||||
|
|
||||||
export default function ProgrammingSchedule({ matrixData }: { matrixData: Record<string, string> }) {
|
export default function ProgrammingSchedule({ matrixData }: { matrixData: Record<string, string> }) {
|
||||||
const [currentTime, setCurrentTime] = useState(new Date());
|
const [currentTime, setCurrentTime] = useState(new Date());
|
||||||
|
const [viewMode, setViewMode] = useState<'day' | 'week'>('day');
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Actualizar cada minuto
|
// Actualizar cada minuto cada que cambie la hora
|
||||||
const interval = setInterval(() => setCurrentTime(new Date()), 60000);
|
const interval = setInterval(() => setCurrentTime(new Date()), 60000);
|
||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const days = [
|
const allDays = [
|
||||||
{ key: 'mon', label: 'Lunes', index: 1 },
|
{ key: 'mon', label: 'Lunes', index: 1 },
|
||||||
{ key: 'tue', label: 'Martes', index: 2 },
|
{ key: 'tue', label: 'Martes', index: 2 },
|
||||||
{ key: 'wed', label: 'Miércoles', index: 3 },
|
{ key: 'wed', label: 'Miércoles', index: 3 },
|
||||||
@ -22,20 +23,26 @@ export default function ProgrammingSchedule({ matrixData }: { matrixData: Record
|
|||||||
{ key: 'sun', label: 'Domingo', index: 0 }
|
{ key: 'sun', label: 'Domingo', index: 0 }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const currentDayIndex = currentTime.getDay();
|
||||||
|
|
||||||
|
// Si estamos en modo día, solo filtramos el día actual
|
||||||
|
const days = viewMode === 'day'
|
||||||
|
? allDays.filter(d => d.index === currentDayIndex)
|
||||||
|
: allDays;
|
||||||
|
|
||||||
const slots = Array.from({length: 48}).map((_, i) => {
|
const slots = Array.from({length: 48}).map((_, i) => {
|
||||||
const h = Math.floor(i/2).toString().padStart(2, '0');
|
const h = Math.floor(i/2).toString().padStart(2, '0');
|
||||||
const m = i % 2 === 0 ? '00' : '30';
|
const m = i % 2 === 0 ? '00' : '30';
|
||||||
return `${h}:${m}`;
|
return `${h}:${m}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
const currentDayIndex = currentTime.getDay();
|
|
||||||
const currentHour = currentTime.getHours();
|
const currentHour = currentTime.getHours();
|
||||||
const currentMinute = currentTime.getMinutes();
|
const currentMinute = currentTime.getMinutes();
|
||||||
const currentSlotIndex = currentHour * 2 + (currentMinute >= 30 ? 1 : 0);
|
const currentSlotIndex = currentHour * 2 + (currentMinute >= 30 ? 1 : 0);
|
||||||
|
|
||||||
// Calc rowSpans to unify cells visually
|
// Calc rowSpans to unify cells visually
|
||||||
const spans: Record<string, number[]> = {};
|
const spans: Record<string, number[]> = {};
|
||||||
days.forEach(d => {
|
allDays.forEach(d => {
|
||||||
const list = slots.map(time => matrixData[`${time}_${d.key}`] || '');
|
const list = slots.map(time => matrixData[`${time}_${d.key}`] || '');
|
||||||
const spanList = new Array(48).fill(1);
|
const spanList = new Array(48).fill(1);
|
||||||
for(let i=0; i<48; i++){
|
for(let i=0; i<48; i++){
|
||||||
@ -58,30 +65,46 @@ export default function ProgrammingSchedule({ matrixData }: { matrixData: Record
|
|||||||
const scrollAmnt = Math.max(0, (currentSlotIndex - 3) * rowHeight);
|
const scrollAmnt = Math.max(0, (currentSlotIndex - 3) * rowHeight);
|
||||||
containerRef.current.scrollTop = scrollAmnt;
|
containerRef.current.scrollTop = scrollAmnt;
|
||||||
}
|
}
|
||||||
}, [currentSlotIndex]);
|
}, [currentSlotIndex, viewMode]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full bg-slate-900 border border-slate-800 rounded-3xl overflow-hidden shadow-2xl relative">
|
<div className="w-full bg-slate-900 border border-slate-800 rounded-3xl overflow-hidden shadow-2xl relative group/matrix">
|
||||||
<div className="p-4 bg-slate-950 border-b border-slate-800 flex justify-between items-center">
|
<div className="p-4 bg-slate-950 border-b border-slate-800 flex flex-col md:flex-row justify-between items-center gap-4">
|
||||||
<h3 className="text-xl font-black font-outfit uppercase tracking-tighter text-white flex items-center gap-3">
|
<h3 className="text-xl font-black font-outfit uppercase tracking-tighter text-white flex items-center gap-3">
|
||||||
<span className="w-3 h-3 rounded-full bg-rose-500 animate-pulse shadow-[0_0_10px_#f43f5e]"></span>
|
<span className="w-3 h-3 rounded-full bg-rose-500 animate-pulse shadow-[0_0_10px_#f43f5e]"></span>
|
||||||
Parrilla de Programación
|
Parrilla de Programación
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
|
{/* Switch Futurista Día / Semana */}
|
||||||
|
<div className="flex items-center gap-2 bg-slate-900 p-1 rounded-xl border border-slate-800">
|
||||||
|
<button
|
||||||
|
onClick={() => setViewMode('day')}
|
||||||
|
className={`px-4 py-1.5 rounded-lg text-xs font-black uppercase tracking-widest transition-all ${viewMode === 'day' ? 'bg-cyan-500 text-slate-950 shadow-[0_0_15px_rgba(6,182,212,0.4)]' : 'text-slate-500 hover:text-slate-300'}`}
|
||||||
|
>
|
||||||
|
Hoy
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setViewMode('week')}
|
||||||
|
className={`px-4 py-1.5 rounded-lg text-xs font-black uppercase tracking-widest transition-all ${viewMode === 'week' ? 'bg-cyan-500 text-slate-950 shadow-[0_0_15px_rgba(6,182,212,0.4)]' : 'text-slate-500 hover:text-slate-300'}`}
|
||||||
|
>
|
||||||
|
Semana
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div ref={containerRef} className="overflow-x-auto overflow-y-auto max-h-[600px] relative custom-scrollbar scroll-smooth">
|
<div ref={containerRef} className="overflow-x-auto overflow-y-auto max-h-[600px] relative custom-scrollbar scroll-smooth">
|
||||||
<table className="w-full text-left border-collapse min-w-[900px] relative">
|
<table className="w-full text-left border-collapse min-w-[300px] md:min-w-[900px] relative">
|
||||||
<thead className="sticky top-0 z-30 ring-1 ring-slate-800">
|
<thead className="sticky top-0 z-30 ring-1 ring-slate-800">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="p-3 w-20 text-center bg-slate-950/90 backdrop-blur-md">
|
<th className="p-3 w-16 md:w-20 text-center bg-slate-950/95 backdrop-blur-md">
|
||||||
<span className="text-xs font-black tracking-widest text-slate-500 uppercase">Hora</span>
|
<span className="text-[10px] font-black tracking-widest text-slate-500 uppercase">Hora</span>
|
||||||
</th>
|
</th>
|
||||||
{days.map(d => {
|
{days.map(d => {
|
||||||
const isToday = d.index === currentDayIndex;
|
const isToday = d.index === currentDayIndex;
|
||||||
return (
|
return (
|
||||||
<th key={d.key} className={`p-4 text-center border-b border-slate-800 bg-slate-900/90 backdrop-blur-md ${isToday ? 'text-cyan-400' : 'text-slate-300'}`}>
|
<th key={d.key} className={`p-4 text-center border-b border-slate-800 bg-slate-900/95 backdrop-blur-md ${isToday ? 'text-cyan-400' : 'text-slate-300'}`}>
|
||||||
<span className={`text-sm tracking-widest uppercase font-black`}>{d.label}</span>
|
<span className={`text-sm tracking-widest uppercase font-black`}>{d.label}</span>
|
||||||
{isToday && <div className="text-[10px] bg-cyan-500/20 text-cyan-300 rounded-full px-2 py-0.5 mt-1 inline-block animate-pulse">HOY</div>}
|
{isToday && viewMode === 'week' && <div className="text-[9px] bg-cyan-500/20 text-cyan-300 rounded-full px-2 py-0.5 mt-1 inline-block animate-pulse">HOY</div>}
|
||||||
</th>
|
</th>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
@ -95,7 +118,7 @@ export default function ProgrammingSchedule({ matrixData }: { matrixData: Record
|
|||||||
return (
|
return (
|
||||||
<tr key={idx} className={`group transition-colors relative ${isCurrentSlot ? 'bg-rose-950/10' : ''}`}>
|
<tr key={idx} className={`group transition-colors relative ${isCurrentSlot ? 'bg-rose-950/10' : ''}`}>
|
||||||
|
|
||||||
{/* Indicador de Línea de Tiempo Rojo (Midiéndose internamente en sus 30 mins) */}
|
{/* Indicador de Línea de Tiempo Roja */}
|
||||||
{isCurrentSlot && (
|
{isCurrentSlot && (
|
||||||
<td className="absolute left-0 right-0 h-0.5 bg-rose-500 z-20 pointer-events-none shadow-[0_0_10px_rgba(244,63,94,0.8)] transition-all ease-linear"
|
<td className="absolute left-0 right-0 h-0.5 bg-rose-500 z-20 pointer-events-none shadow-[0_0_10px_rgba(244,63,94,0.8)] transition-all ease-linear"
|
||||||
style={{ top: `${((currentMinute % 30) / 30) * 100}%` }}></td>
|
style={{ top: `${((currentMinute % 30) / 30) * 100}%` }}></td>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user