Files
beStream/src/components/calendar/UpcomingList.tsx
2025-12-14 12:57:37 +01:00

178 lines
5.8 KiB
TypeScript

import { motion } from 'framer-motion';
import { Film, Tv, Music, Calendar, Check, Clock } from 'lucide-react';
import type { CalendarItem } from '../../types/unified';
import Badge from '../ui/Badge';
interface UpcomingListProps {
items: CalendarItem[];
isLoading?: boolean;
onItemClick?: (item: CalendarItem) => void;
}
export default function UpcomingList({
items,
isLoading = false,
onItemClick,
}: UpcomingListProps) {
const getTypeIcon = (type: CalendarItem['type']) => {
switch (type) {
case 'movie':
return <Film size={16} className="text-red-400" />;
case 'episode':
return <Tv size={16} className="text-blue-400" />;
case 'album':
return <Music size={16} className="text-purple-400" />;
}
};
const getTypeBadge = (type: CalendarItem['type']) => {
switch (type) {
case 'movie':
return <Badge size="sm" className="bg-red-500/20 text-red-400">Movie</Badge>;
case 'episode':
return <Badge size="sm" className="bg-blue-500/20 text-blue-400">Episode</Badge>;
case 'album':
return <Badge size="sm" className="bg-purple-500/20 text-purple-400">Album</Badge>;
}
};
const formatDate = (date: Date) => {
const now = new Date();
const diff = date.getTime() - now.getTime();
const days = Math.ceil(diff / (1000 * 60 * 60 * 24));
if (days === 0) return 'Today';
if (days === 1) return 'Tomorrow';
if (days < 7) return `In ${days} days`;
return date.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: date.getFullYear() !== now.getFullYear() ? 'numeric' : undefined
});
};
// Group items by date
const groupedItems = items.reduce((acc, item) => {
const dateKey = item.date.toDateString();
if (!acc[dateKey]) {
acc[dateKey] = { date: item.date, items: [] };
}
acc[dateKey].items.push(item);
return acc;
}, {} as Record<string, { date: Date; items: CalendarItem[] }>);
const sortedGroups = Object.values(groupedItems).sort(
(a, b) => a.date.getTime() - b.date.getTime()
);
if (isLoading) {
return (
<div className="space-y-4">
{Array.from({ length: 5 }).map((_, i) => (
<div key={i} className="glass rounded-lg p-4 animate-pulse">
<div className="h-4 bg-white/10 rounded w-24 mb-3" />
<div className="space-y-2">
<div className="h-16 bg-white/5 rounded" />
</div>
</div>
))}
</div>
);
}
if (items.length === 0) {
return (
<div className="glass rounded-lg p-8 text-center">
<Calendar size={48} className="mx-auto text-gray-500 mb-4" />
<h3 className="text-lg font-medium text-gray-300">No upcoming releases</h3>
<p className="text-gray-500 mt-1">Check back later for new content</p>
</div>
);
}
return (
<div className="space-y-6">
{sortedGroups.map(({ date, items: dateItems }) => (
<div key={date.toDateString()}>
{/* Date Header */}
<div className="flex items-center gap-2 mb-3">
<Calendar size={16} className="text-gray-400" />
<h3 className="font-medium text-gray-300">
{formatDate(date)}
</h3>
<span className="text-sm text-gray-500">
{date.toLocaleDateString('en-US', { weekday: 'long' })}
</span>
</div>
{/* Items */}
<div className="space-y-2">
{dateItems.map((item) => (
<motion.div
key={item.id}
whileHover={{ scale: 1.01, x: 4 }}
onClick={() => onItemClick?.(item)}
className="glass rounded-lg p-4 flex items-center gap-4 cursor-pointer hover:bg-white/5 transition-colors"
>
{/* Poster */}
{item.poster ? (
<img
src={item.poster}
alt={item.title}
className="w-12 h-16 object-cover rounded"
/>
) : (
<div className="w-12 h-16 rounded bg-white/10 flex items-center justify-center">
{getTypeIcon(item.type)}
</div>
)}
{/* Info */}
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1">
{getTypeIcon(item.type)}
<h4 className="font-medium text-white line-clamp-1">
{item.title}
</h4>
</div>
{item.subtitle && (
<p className="text-sm text-gray-400 line-clamp-1">
{item.subtitle}
</p>
)}
<div className="flex items-center gap-2 mt-2">
{getTypeBadge(item.type)}
<span className="text-xs text-gray-500 flex items-center gap-1">
<Clock size={10} />
{date.toLocaleTimeString('en-US', {
hour: 'numeric',
minute: '2-digit'
})}
</span>
</div>
</div>
{/* Status */}
<div>
{item.hasFile ? (
<Badge variant="success" className="flex items-center gap-1">
<Check size={12} />
Available
</Badge>
) : (
<Badge variant="info" className="flex items-center gap-1">
<Clock size={12} />
Upcoming
</Badge>
)}
</div>
</motion.div>
))}
</div>
</div>
))}
</div>
);
}