almeira-web/src/components/ProgrammingSchedule.tsx
Carlos (Almeira) e3024af88b 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
2026-03-27 06:51:16 -05:00

170 lines
9.7 KiB
TypeScript

'use client';
import { useEffect, useState, useRef } from 'react';
export default function ProgrammingSchedule({ matrixData }: { matrixData: Record<string, string> }) {
const [currentTime, setCurrentTime] = useState(new Date());
const [viewMode, setViewMode] = useState<'day' | 'week'>('day');
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
// Actualizar cada minuto cada que cambie la hora
const interval = setInterval(() => setCurrentTime(new Date()), 60000);
return () => clearInterval(interval);
}, []);
const allDays = [
{ key: 'mon', label: 'Lunes', index: 1 },
{ key: 'tue', label: 'Martes', index: 2 },
{ key: 'wed', label: 'Miércoles', index: 3 },
{ key: 'thu', label: 'Jueves', index: 4 },
{ key: 'fri', label: 'Viernes', index: 5 },
{ key: 'sat', label: 'Sábado', index: 6 },
{ 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 h = Math.floor(i/2).toString().padStart(2, '0');
const m = i % 2 === 0 ? '00' : '30';
return `${h}:${m}`;
});
const currentHour = currentTime.getHours();
const currentMinute = currentTime.getMinutes();
const currentSlotIndex = currentHour * 2 + (currentMinute >= 30 ? 1 : 0);
// Calc rowSpans to unify cells visually
const spans: Record<string, number[]> = {};
allDays.forEach(d => {
const list = slots.map(time => matrixData[`${time}_${d.key}`] || '');
const spanList = new Array(48).fill(1);
for(let i=0; i<48; i++){
if (spanList[i] === 0) continue;
let count = 1;
const val = list[i];
while(i + count < 48 && list[i + count] === val && val !== '') {
spanList[i + count] = 0;
count++;
}
spanList[i] = count;
}
spans[d.key] = spanList;
});
// Auto-scroll a la hora actual al cargar
useEffect(() => {
if (containerRef.current) {
const rowHeight = 40; // approx
const scrollAmnt = Math.max(0, (currentSlotIndex - 3) * rowHeight);
containerRef.current.scrollTop = scrollAmnt;
}
}, [currentSlotIndex, viewMode]);
return (
<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 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">
<span className="w-3 h-3 rounded-full bg-rose-500 animate-pulse shadow-[0_0_10px_#f43f5e]"></span>
Parrilla de Programación
</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 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-[300px] md:min-w-[900px] relative">
<thead className="sticky top-0 z-30 ring-1 ring-slate-800">
<tr>
<th className="p-3 w-16 md:w-20 text-center bg-slate-950/95 backdrop-blur-md">
<span className="text-[10px] font-black tracking-widest text-slate-500 uppercase">Hora</span>
</th>
{days.map(d => {
const isToday = d.index === currentDayIndex;
return (
<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>
{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>
)
})}
</tr>
</thead>
<tbody className="relative">
{slots.map((time, idx) => {
const isHalfHour = time.endsWith('30');
const isCurrentSlot = idx === currentSlotIndex;
return (
<tr key={idx} className={`group transition-colors relative ${isCurrentSlot ? 'bg-rose-950/10' : ''}`}>
{/* Indicador de Línea de Tiempo Roja */}
{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"
style={{ top: `${((currentMinute % 30) / 30) * 100}%` }}></td>
)}
<td className={`p-2 border-r border-slate-800 bg-slate-950 text-center sticky left-0 z-20 ${isHalfHour ? 'border-b border-dashed border-slate-800/50' : 'border-b border-solid'}`}>
<span className={`font-mono text-xs tracking-widest font-black ${isCurrentSlot ? 'text-rose-400' : isHalfHour ? 'text-slate-600' : 'text-slate-400'}`}>
{time}
</span>
</td>
{days.map(d => {
const span = spans[d.key][idx];
if (span === 0) return null; // Es parte de un bloque unificado, no se renderiza
const program = matrixData[`${time}_${d.key}`];
const isToday = d.index === currentDayIndex;
// Es en vivo si la ranura actual recae DENTRO del span unificado
const isLiveNow = isToday && currentSlotIndex >= idx && currentSlotIndex < idx + span;
return (
<td key={d.key} rowSpan={span} className={`
p-2 border-r border-slate-800/50 relative text-center
${isHalfHour && span === 1 ? 'border-b border-dashed border-slate-800/20' : 'border-b border-solid'}
${isLiveNow ? 'bg-rose-900/30 ring-1 ring-inset ring-rose-500 shadow-inner' : isToday ? 'bg-slate-800/30 hover:bg-slate-800/50 -z-10' : 'hover:bg-slate-800/40'}
`}>
{isLiveNow && (
<div className="absolute top-1 right-1 flex items-center gap-1 bg-rose-500 justify-center text-white text-[9px] font-black tracking-widest uppercase px-1.5 rounded animate-pulse z-10 shadow-lg">
Al Aire
</div>
)}
<div className={`text-sm font-bold flex flex-col items-center justify-center h-full min-h-[30px] w-full ${isLiveNow ? 'text-white drop-shadow-[0_0_8px_rgba(255,255,255,0.7)]' : program ? 'text-slate-300' : 'text-slate-700/50 font-normal italic'}`}>
<span>{program || '-'}</span>
{span > 1 && program && <span className="text-[10px] text-cyan-400 font-black tracking-widest uppercase mt-1 opacity-70">({span * 30} MINS)</span>}
</div>
</td>
)
})}
</tr>
)
})}
</tbody>
</table>
</div>
</div>
);
}