dptxa-proxy / src /pages /lich-chieu.astro
TXAVLOG
Deploy DPTXA to Hugging Face Spaces
4bea261
Raw
History Blame Contribute Delete
9.6 kB
---
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>