Spaces:
Sleeping
Sleeping
File size: 9,604 Bytes
4bea261 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 | ---
import Layout from '../layouts/Layout.astro';
import { formatImageUrl } from '../lib/api.js';
import { txasupabase } from '../lib/txasupabase.js';
// Lấy tất cả phim có lịch chiếu tập tiếp theo
let scheduledMovies = [];
try {
const localMovies = await txasupabase.getLocalMovies().catch(() => ({}));
scheduledMovies = Object.entries(localMovies)
.map(([slug, m]) => {
if (!m || !m.movie) return null;
return {
...m.movie,
slug
};
})
.filter(Boolean)
.filter(movie => movie.next_episode_time)
.filter(movie => {
const nextTime = new Date(movie.next_episode_time);
const minTime = new Date(Date.now() - 7 * 86400000);
return nextTime >= minTime;
})
.sort((a, b) => new Date(a.next_episode_time).getTime() - new Date(b.next_episode_time).getTime());
} catch (e) {
console.error('[lich-chieu] Error fetching schedule:', e);
}
// Nhóm phim theo ngày
const getVietnamDateString = (date) => {
return new Date(date).toLocaleDateString('en-CA', { timeZone: 'Asia/Ho_Chi_Minh' });
};
const today = new Date(new Date().toLocaleDateString('en-US', { timeZone: 'Asia/Ho_Chi_Minh' }));
today.setHours(0, 0, 0, 0);
// Tạo danh sách 7 ngày từ hôm nay
const days = [];
for (let i = 0; i < 7; i++) {
const d = new Date(today);
d.setDate(d.getDate() + i);
days.push(d);
}
const dayNames = ['Chủ Nhật', 'Thứ Hai', 'Thứ Ba', 'Thứ Tư', 'Thứ Năm', 'Thứ Sáu', 'Thứ Bảy'];
function getMoviesForDate(date: Date) {
const targetStr = getVietnamDateString(date);
return scheduledMovies.filter(m => {
return getVietnamDateString(m.next_episode_time) === targetStr;
});
}
---
<Layout title="Lịch chiếu phim - Lịch phát sóng tập mới" description="Xem lịch chiếu phim, lịch phát sóng tập mới nhất hàng ngày tại DPTXA." slug="/lich-chieu">
<div class="min-h-screen pb-24">
<div class="mx-auto max-w-7xl px-4">
<!-- Header -->
<div class="mb-8">
<div class="flex items-center gap-3 text-[10px] font-black uppercase tracking-widest text-gray-500 mb-4">
<a href="/" class="hover:text-primary transition-colors">Trang chủ</a>
<i class="fas fa-chevron-right text-[8px] opacity-30"></i>
<span class="text-primary">Lịch chiếu</span>
</div>
<h1 class="text-3xl font-black text-white tracking-tight flex items-center gap-3">
<i class="fas fa-calendar-alt text-primary"></i> Lịch chiếu
</h1>
</div>
<!-- Date Tabs -->
<div class="relative mb-8">
<div id="date-tabs" class="flex items-center gap-2 overflow-x-auto pb-2 scrollbar-thin">
{days.map((d, i) => {
const dd = String(d.getDate()).padStart(2, '0');
const mm = String(d.getMonth() + 1).padStart(2, '0');
const dayName = dayNames[d.getDay()];
const isToday = i === 0;
const count = getMoviesForDate(d).length;
return (
<button
data-tab-index={i}
class={`schedule-tab flex flex-col items-center shrink-0 px-6 py-3 rounded-2xl border transition-all cursor-pointer ${
isToday
? 'bg-primary/15 border-primary/30 text-primary'
: 'bg-white/[0.03] border-white/[0.06] text-gray-400 hover:bg-white/5 hover:text-white'
}`}
>
<span class="text-[10px] font-bold text-gray-500">{dd}/{mm}</span>
<span class={`text-sm font-black ${isToday ? 'text-primary' : ''}`}>{dayName}</span>
{count > 0 && (
<span class="mt-1 text-[9px] font-bold bg-primary/20 text-primary px-2 py-0.5 rounded-full">{count} phim</span>
)}
</button>
);
})}
</div>
</div>
<!-- Movie Lists per Day -->
{days.map((d, i) => {
const moviesForDay = getMoviesForDate(d);
return (
<div data-panel-index={i} class={`schedule-panel ${i === 0 ? '' : 'hidden'}`}>
{moviesForDay.length === 0 ? (
<div class="text-center py-24 glass rounded-[32px] border border-white/5 p-8 relative overflow-hidden">
<div class="absolute -right-20 -top-20 h-40 w-40 rounded-full bg-white/[0.01] blur-3xl"></div>
<div class="h-16 w-16 rounded-2xl bg-white/[0.03] border border-white/5 flex items-center justify-center mx-auto mb-4 text-gray-600 shadow-inner">
<i class="far fa-calendar-times text-2xl"></i>
</div>
<p class="text-gray-400 text-sm font-bold tracking-tight">Không có phim nào dự kiến phát sóng vào ngày này.</p>
<p class="text-xs text-gray-600 mt-1">Đón xem các cập nhật mới nhất từ ban quản trị hệ thống.</p>
</div>
) : (
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{moviesForDay.map(m => {
const poster = formatImageUrl(m.poster_url || m.thumb_url);
const epLabel = m.next_episode_name || m.episode_current || 'Tập mới';
const timeStr = new Date(m.next_episode_time).toLocaleTimeString('vi-VN', { hour: '2-digit', minute: '2-digit', hour12: false, timeZone: 'Asia/Ho_Chi_Minh' });
return (
<a href={`/phim/${m.slug}`} class="relative flex items-center gap-4 rounded-3xl bg-gradient-to-br from-white/[0.03] to-white/[0.01] border border-white/5 p-4 hover:bg-white/[0.06] hover:border-primary/30 transition-all duration-300 group shadow-lg overflow-hidden">
<!-- Ambient Glow -->
<div class="absolute -right-8 -bottom-8 h-24 w-24 rounded-full bg-primary/5 blur-2xl group-hover:bg-primary/10 transition-colors duration-300"></div>
<!-- Movie Thumbnail -->
<div class="h-20 w-16 rounded-2xl overflow-hidden shrink-0 border border-white/10 shadow-md relative">
<img src={poster} alt={m.name} class="h-full w-full object-cover group-hover:scale-110 transition-transform duration-500" loading="lazy" />
<div class="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent"></div>
</div>
<!-- Movie Meta -->
<div class="min-w-0 flex-1 space-y-1">
<span class="inline-flex items-center gap-1.5 text-[9px] font-black uppercase tracking-wider text-gray-500">
<span class="h-1.5 w-1.5 rounded-full bg-primary animate-pulse"></span>
Sắp phát sóng
</span>
<div class="text-sm font-black text-white truncate group-hover:text-primary transition-colors duration-300">{m.name}</div>
<div class="flex items-center gap-2 mt-1.5">
<span class="text-[10px] font-black bg-primary/10 border border-primary/20 text-primary px-2.5 py-1 rounded-xl flex items-center gap-1">
<i class="far fa-clock"></i>
{timeStr}
</span>
<span class="text-[10px] font-bold text-gray-400">
{epLabel}
</span>
</div>
</div>
<!-- Action Icon -->
<div class="h-8 w-8 rounded-full bg-white/[0.03] border border-white/5 flex items-center justify-center shrink-0 group-hover:bg-primary/20 group-hover:border-primary/30 transition-all duration-300 text-gray-500 group-hover:text-primary shadow-inner">
<i class="fas fa-chevron-right text-xs group-hover:translate-x-0.5 transition-transform"></i>
</div>
</a>
);
})}
</div>
)}
</div>
);
})}
</div>
</div>
</Layout>
<script>
document.addEventListener('astro:page-load', () => {
const tabs = document.querySelectorAll('.schedule-tab');
const panels = document.querySelectorAll('.schedule-panel');
tabs.forEach(tab => {
tab.addEventListener('click', () => {
const idx = tab.getAttribute('data-tab-index');
// Reset all tabs
tabs.forEach(t => {
t.classList.remove('bg-primary/15', 'border-primary/30', 'text-primary');
t.classList.add('bg-white/[0.03]', 'border-white/[0.06]', 'text-gray-400');
const nameSpan = t.querySelector('.text-sm');
if (nameSpan) nameSpan.classList.remove('text-primary');
});
// Activate clicked tab
tab.classList.add('bg-primary/15', 'border-primary/30', 'text-primary');
tab.classList.remove('bg-white/[0.03]', 'border-white/[0.06]', 'text-gray-400');
const nameSpan = tab.querySelector('.text-sm');
if (nameSpan) nameSpan.classList.add('text-primary');
// Show/hide panels
panels.forEach(p => {
if (p.getAttribute('data-panel-index') === idx) {
p.classList.remove('hidden');
} else {
p.classList.add('hidden');
}
});
});
});
});
</script>
|