dptxa-proxy / src /pages /admin /phim /index.astro
TXAVLOG
Deploy DPTXA to Hugging Face Spaces
4bea261
Raw
History Blame Contribute Delete
33.8 kB
---
import AdminLayout from '../../../layouts/AdminLayout.astro';
import { getNewestMovies, searchMovies, formatImageUrl } from '../../../lib/api.js';
import { getLocalMovies, getDeletedMovies } from '../../../lib/localDb.js';
// Kiểm tra phiên đăng nhập và quyền Admin được thực hiện tự động trong AdminLayout
const page = parseInt(Astro.url.searchParams.get('page') || '1');
const keyword = Astro.url.searchParams.get('keyword') || '';
const tab = Astro.url.searchParams.get('tab') || 'all'; // all, local, deleted
// Đọc database cục bộ
const localMovies = await getLocalMovies();
const deletedSlugs = await getDeletedMovies();
let items = [];
let pagination = { currentPage: page, totalPages: 1, totalItems: 0 };
if (tab === 'local') {
// Chỉ lấy phim tự tạo hoặc đã chỉnh sửa lưu trong movies.json
const allLocal = Object.values(localMovies);
items = allLocal.map(m => ({
_id: m.movie.id || m.movie.slug,
name: m.movie.name,
slug: m.movie.slug,
origin_name: m.movie.origin_name,
thumb_url: m.movie.thumb_url,
poster_url: m.movie.poster_url,
year: m.movie.year,
quality: m.movie.quality || 'FHD',
lang: m.movie.lang || 'Vietsub',
episode_current: m.movie.episode_current || 'Tập 1',
isLocal: m.movie.isLocal,
hasLocalEdits: !m.movie.isLocal
}));
// Lọc tìm kiếm trên tập local
if (keyword) {
const k = keyword.toLowerCase();
items = items.filter(item =>
item.name?.toLowerCase().includes(k) ||
item.origin_name?.toLowerCase().includes(k) ||
item.slug?.includes(k)
);
}
pagination = {
currentPage: 1,
totalPages: 1,
totalItems: items.length
};
} else if (tab === 'deleted') {
// Chỉ lấy danh sách các phim đã bị ẩn
items = deletedSlugs.map(slug => {
const local = localMovies[slug];
return {
_id: slug,
slug,
name: local?.movie?.name || `Phim từ API (${slug})`,
origin_name: local?.movie?.origin_name || 'Đã ẩn',
thumb_url: local?.movie?.thumb_url || '',
year: local?.movie?.year || '',
isDeleted: true
};
});
if (keyword) {
const k = keyword.toLowerCase();
items = items.filter(item =>
item.name?.toLowerCase().includes(k) ||
item.slug?.includes(k)
);
}
pagination = {
currentPage: 1,
totalPages: 1,
totalItems: items.length
};
} else {
// Tab 'all': Lấy từ API và trộn dữ liệu cục bộ qua api.js
if (keyword) {
const result = await searchMovies(keyword, page, { grouped: false });
items = result.items || [];
pagination = result.pagination || pagination;
} else {
const result = await getNewestMovies(page, { grouped: false });
items = result.items || [];
pagination = result.pagination || pagination;
}
}
// Thống kê số lượng
const countLocalCustom = Object.values(localMovies).filter(m => m.movie.isLocal).length;
const countLocalEdits = Object.values(localMovies).filter(m => !m.movie.isLocal).length;
const countDeleted = deletedSlugs.length;
const stats = [
{ label: 'Phim tự tạo', value: countLocalCustom, icon: 'fas fa-plus-circle', color: 'text-emerald-500' },
{ label: 'Phim đã sửa', value: countLocalEdits, icon: 'fas fa-edit', color: 'text-amber-500' },
{ label: 'Phim đã ẩn', value: countDeleted, icon: 'fas fa-eye-slash', color: 'text-red-500' },
];
---
<AdminLayout title="Quản lý phim">
<div class="mb-12 flex flex-col gap-6 md:flex-row md:items-center md:justify-between">
<div>
<h1 class="text-4xl font-black tracking-tighter text-white">Qun lý phim</h1>
<p class="mt-2 text-gray-500">Chnh sa, thêm bt tp phim, ẩn hin phim và cp nht ngun video hàng lot.</p>
</div>
<div class="flex gap-4">
<a href="/admin/phim/them-moi" class="flex items-center gap-2 rounded-2xl bg-primary px-6 py-4 text-xs font-black uppercase tracking-widest text-dark hover:scale-105 transition-all shadow-[0_0_20px_var(--primary)]">
<i class="fas fa-plus"></i>
Thêm phim mi
</a>
</div>
</div>
<!-- Thng kê nhanh -->
<div class="grid grid-cols-1 gap-6 sm:grid-cols-3 mb-10">
{stats.map(stat => (
<div class="glass rounded-3xl p-6 border border-white/5 relative overflow-hidden group">
<div class="relative z-10 flex items-center gap-4">
<div class={`h-12 w-12 rounded-2xl bg-white/5 flex items-center justify-center ${stat.color} text-xl`}>
<i class={stat.icon}></i>
</div>
<div>
<p class="text-[10px] font-black uppercase tracking-widest text-gray-500">{stat.label}</p>
<h3 class="text-2xl font-black text-white mt-1">{stat.value}</h3>
</div>
</div>
</div>
))}
</div>
<!-- Search & Tab Filter -->
<div class="mb-8 flex flex-col gap-6 lg:flex-row lg:items-center lg:justify-between">
<!-- Tabs -->
<div class="flex border-b border-white/5">
<a href="?tab=all" class={`px-6 py-4 text-xs font-black uppercase tracking-widest border-b-2 transition-all ${tab === 'all' ? 'text-primary border-primary' : 'text-gray-500 border-transparent hover:text-white'}`}>Tất cả danh mục</a>
<a href="?tab=local" class={`px-6 py-4 text-xs font-black uppercase tracking-widest border-b-2 transition-all ${tab === 'local' ? 'text-primary border-primary' : 'text-gray-500 border-transparent hover:text-white'}`}>Phim Tự tạo / Đã sửa ({countLocalCustom + countLocalEdits})</a>
<a href="?tab=deleted" class={`px-6 py-4 text-xs font-black uppercase tracking-widest border-b-2 transition-all ${tab === 'deleted' ? 'text-primary border-primary' : 'text-gray-500 border-transparent hover:text-white'}`}>Phim đã ẩn ({countDeleted})</a>
</div>
<!-- Search -->
<form method="GET" class="relative max-w-md w-full">
<input type="hidden" name="tab" value={tab} />
<input
type="text"
name="keyword"
placeholder="Tìm phim theo tên, slug..."
value={keyword}
class="w-full bg-[#12141d] border border-white/5 rounded-2xl px-6 py-4 text-sm text-white placeholder-gray-500 focus:outline-none focus:border-primary/50 transition-all pl-12"
/>
<i class="fas fa-search absolute left-5 top-1/2 -translate-y-1/2 text-gray-500"></i>
{keyword && (
<a href={`?tab=${tab}`} class="absolute right-5 top-1/2 -translate-y-1/2 text-gray-500 hover:text-white transition-colors">
<i class="fas fa-times"></i>
</a>
)}
</form>
</div>
<!-- Movie Catalog Table -->
<div class="glass rounded-[40px] p-6 md:p-10 border border-white/5 overflow-hidden">
<div class="overflow-x-auto">
<table class="w-full text-left" id="movies-table">
<thead>
<tr class="border-b border-white/5">
<th class="pb-6 text-center w-12">
<input type="checkbox" id="select-all" class="h-4 w-4 rounded border-white/10 bg-white/5 text-primary focus:ring-primary focus:ring-offset-0 cursor-pointer" />
</th>
<th class="pb-6 text-[10px] font-black uppercase tracking-widest text-gray-500">Phim</th>
<th class="pb-6 text-[10px] font-black uppercase tracking-widest text-gray-500">Năm / Loi</th>
<th class="pb-6 text-[10px] font-black uppercase tracking-widest text-gray-500">Trng thái</th>
<th class="pb-6 text-[10px] font-black uppercase tracking-widest text-gray-500">Ngun gc</th>
<th class="pb-6 text-[10px] font-black uppercase tracking-widest text-gray-500 text-right">Hành động</th>
</tr>
</thead>
<tbody class="divide-y divide-white/5">
{items.length === 0 ? (
<tr>
<td colspan="6" class="py-12 text-center text-gray-500">
<i class="fas fa-film text-4xl mb-4 block opacity-30"></i>
Không tìm thy phim nào phù hp!
</td>
</tr>
) : (
items.map(item => {
const isLocal = item.isLocal;
const hasLocalEdits = localMovies[item.slug] && !isLocal;
const isDeleted = item.isDeleted;
const thumb = item.thumb_url ? formatImageUrl(item.thumb_url) : '/no-cover.jpg';
const require_login = localMovies[item.slug]?.movie?.require_login === true || localMovies[item.slug]?.movie?.only_login === true;
return (
<tr class="group hover:bg-white/[0.02] transition-colors" data-slug={item.slug}>
<td class="py-6 text-center">
<input type="checkbox" class="movie-checkbox h-4 w-4 rounded border-white/10 bg-white/5 text-primary focus:ring-primary focus:ring-offset-0 cursor-pointer" data-slug={item.slug} />
</td>
<td class="py-6">
<div class="flex items-center gap-4">
<div class="h-16 w-12 overflow-hidden rounded-xl bg-white/5 flex-shrink-0 border border-white/5">
<img src={thumb} class="h-full w-full object-cover" alt="" onerror="this.src='/no-cover.jpg'" />
</div>
<div class="flex flex-col">
<div class="flex items-center gap-2">
<span class="text-sm font-bold text-white group-hover:text-primary transition-colors">{item.name}</span>
{require_login && (
<span class="inline-flex items-center gap-1 rounded bg-violet-500/10 px-1.5 py-0.5 text-[9px] font-black uppercase tracking-wider text-violet-400 border border-violet-500/20" data-txatooltip="Bt buc đăng nhp để xem">
<i class="fas fa-lock text-[8px]"></i> Login
</span>
)}
</div>
<span class="text-xs text-gray-500 line-clamp-1">{item.origin_name}</span>
<span class="text-[9px] text-gray-600 font-mono select-all mt-0.5">{item.slug}</span>
</div>
</div>
</td>
<td class="py-6 text-xs text-gray-400">
<div class="flex flex-col">
<span>{item.year || 'N/A'}</span>
<span class="text-[10px] text-gray-600 uppercase tracking-wider font-semibold">{item.type || 'N/A'}</span>
</div>
</td>
<td class="py-6 text-xs font-bold text-gray-300">
<div class="flex flex-col">
<span>{item.episode_current || 'N/A'}</span>
</div>
</td>
<td class="py-6">
{isLocal ? (
<span class="inline-flex rounded-xl bg-emerald-500/10 px-3 py-1 text-[9px] font-black uppercase tracking-widest text-emerald-400 border border-emerald-500/20">Ni bộ</span>
) : hasLocalEdits ? (
<span class="inline-flex rounded-xl bg-amber-500/10 px-3 py-1 text-[9px] font-black uppercase tracking-widest text-amber-400 border border-amber-500/20">Đã sa</span>
) : isDeleted ? (
<span class="inline-flex rounded-xl bg-red-500/10 px-3 py-1 text-[9px] font-black uppercase tracking-widest text-red-400 border border-red-500/20">Đã ẩn</span>
) : (
<span class="inline-flex rounded-xl bg-blue-500/10 px-3 py-1 text-[9px] font-black uppercase tracking-widest text-blue-400 border border-blue-500/20">API Gc</span>
)}
</td>
<td class="py-6 text-right">
<div class="flex justify-end gap-2">
{!isDeleted && (
<a href={`/admin/phim/chinh-sua?slug=${item.slug}`} class="h-9 w-9 rounded-xl bg-white/5 flex items-center justify-center text-gray-400 hover:text-primary transition-colors border border-white/5 active:scale-95" data-txatooltip="Chnh sa toàn bphim + tp">
<i class="fas fa-edit text-xs"></i>
</a>
)}
{isDeleted ? (
<button class="restore-single-btn h-9 w-9 rounded-xl bg-emerald-500/10 hover:bg-emerald-500/20 flex items-center justify-center text-emerald-400 transition-colors border border-emerald-500/20 active:scale-95" data-slug={item.slug} data-txatooltip="Khôi phục hiển thị phim">
<i class="fas fa-eye text-xs"></i>
</button>
) : (
<button class="delete-single-btn h-9 w-9 rounded-xl bg-red-500/10 hover:bg-red-500/20 flex items-center justify-center text-red-400 transition-colors border border-red-500/20 active:scale-95" data-slug={item.slug} data-txatooltip="Ẩn / Xóa phim">
<i class="fas fa-eye-slash text-xs"></i>
</button>
)}
{hasLocalEdits && (
<button class="reset-single-btn h-9 w-9 rounded-xl bg-amber-500/10 hover:bg-amber-500/20 flex items-center justify-center text-amber-400 transition-colors border border-amber-500/20 active:scale-95" data-slug={item.slug} data-txatooltip="Đặt lại về dữ liệu API gốc">
<i class="fas fa-undo text-xs"></i>
</button>
)}
</div>
</td>
</tr>
);
})
)}
</tbody>
</table>
</div>
<!-- Pagination (Only for Tab 'all') -->
{tab === 'all' && pagination.totalPages > 1 && (
<div class="mt-12 flex flex-wrap items-center justify-between gap-6 border-t border-white/5 pt-8">
<span class="text-xs text-gray-500">
Trang <strong>{pagination.currentPage}</strong> / {pagination.totalPages}
</span>
<div class="flex items-center gap-2">
{pagination.currentPage > 1 && (
<a href={`?tab=all&page=${pagination.currentPage - 1}&keyword=${keyword}`} class="h-10 px-4 rounded-xl bg-white/5 hover:bg-white/10 flex items-center justify-center text-xs font-bold text-white border border-white/5 transition-all">Trang trước</a>
)}
<!-- Simple dynamic range -->
{[-2, -1, 0, 1, 2].map(offset => {
const pageNum = pagination.currentPage + offset;
if (pageNum > 0 && pagination.totalPages >= pageNum) {
return (
<a
href={`?tab=all&page=${pageNum}&keyword=${keyword}`}
class={`h-10 w-10 rounded-xl flex items-center justify-center text-xs font-bold border transition-all ${pageNum === pagination.currentPage ? 'bg-primary border-primary text-dark font-black' : 'bg-white/5 border-white/5 text-gray-400 hover:bg-white/10 hover:text-white'}`}
>
{pageNum}
</a>
);
}
return null;
})}
{pagination.currentPage < pagination.totalPages && (
<a href={`?tab=all&page=${pagination.currentPage + 1}&keyword=${keyword}`} class="h-10 px-4 rounded-xl bg-white/5 hover:bg-white/10 flex items-center justify-center text-xs font-bold text-white border border-white/5 transition-all">Trang tiếp</a>
)}
</div>
</div>
)}
</div>
<!-- FLOATING BULK ACTIONS BAR (PILL STYLE) -->
<div id="bulk-bar" class="fixed bottom-8 left-1/2 -translate-x-1/2 z-[100] w-[95%] max-w-4xl bg-slate-950/90 border border-white/10 rounded-full shadow-[0_20px_60px_rgba(0,0,0,0.9)] backdrop-blur-2xl px-6 py-3 flex flex-wrap items-center justify-between gap-3 transform translate-y-32 opacity-0 transition-all duration-500 ease-out pointer-events-none">
<div class="flex items-center gap-3 pl-2">
<div class="h-7 w-7 rounded-full bg-primary/20 flex items-center justify-center text-primary text-xs font-black shadow-[0_0_15px_rgba(229,9,20,0.3)]" id="bulk-count">0</div>
<span class="text-[11px] font-black uppercase tracking-widest text-gray-300 hidden sm:inline">Phim đã chn</span>
</div>
<div class="flex flex-wrap items-center gap-2">
{/* Set nhanh trạng thái */}
<div class="flex items-center gap-1 bg-white/5 rounded-full p-1 border border-white/5">
<span class="text-[9px] font-black uppercase tracking-widest text-gray-500 px-2">Trng thái:</span>
<button data-status="ongoing" class="bulk-status-btn px-3 py-1.5 rounded-full text-[9px] font-black uppercase tracking-wider text-amber-400 hover:bg-white/5 transition-all">Đang chiếu</button>
<button data-status="completed" class="bulk-status-btn px-3 py-1.5 rounded-full text-[9px] font-black uppercase tracking-wider text-emerald-400 hover:bg-white/5 transition-all">Hoàn thành</button>
<button data-status="trailer" class="bulk-status-btn px-3 py-1.5 rounded-full text-[9px] font-black uppercase tracking-wider text-blue-400 hover:bg-white/5 transition-all">Sp chiếu</button>
</div>
{/* Ẩn / Hiện */}
<div class="flex items-center gap-1 bg-white/5 rounded-full p-1 border border-white/5">
<button id="bulk-hide-btn" class="flex items-center gap-1 px-3 py-1.5 rounded-full text-red-400 hover:bg-red-500/10 text-[9px] font-black uppercase tracking-widest transition-all">
<i class="fas fa-eye-slash text-[8px]"></i> Ẩn
</button>
<button id="bulk-show-btn" class="flex items-center gap-1 px-3 py-1.5 rounded-full text-emerald-400 hover:bg-emerald-500/10 text-[9px] font-black uppercase tracking-widest transition-all">
<i class="fas fa-eye text-[8px]"></i> Hin
</button>
</div>
{/* Yêu cầu đăng nhập */}
<div class="flex items-center gap-1 bg-white/5 rounded-full p-1 border border-white/5">
<button id="bulk-lock-btn" class="flex items-center gap-1 px-3 py-1.5 rounded-full text-violet-400 hover:bg-violet-500/10 text-[9px] font-black uppercase tracking-widest transition-all" data-txatooltip="Bt buc đăng nhp để xem phim">
<i class="fas fa-lock text-[8px]"></i> Khóa Login
</button>
<button id="bulk-unlock-btn" class="flex items-center gap-1 px-3 py-1.5 rounded-full text-gray-400 hover:bg-white/10 text-[9px] font-black uppercase tracking-widest transition-all" data-txatooltip="Không yêu cu đăng nhp khi xem phim">
<i class="fas fa-unlock text-[8px]"></i> Mtdo
</button>
</div>
{/* Xóa vĩnh viễn */}
<button id="bulk-hard-delete-btn" class="flex items-center gap-1.5 px-4 py-2.5 rounded-full bg-red-600 hover:bg-red-700 text-white text-[9px] font-black uppercase tracking-widest transition-all shadow-lg active:scale-95">
<i class="fas fa-trash-alt"></i> Xóa Hn
</button>
{/* Đặt lại gốc (chỉ hiển thị ở tab tương thích) */}
{tab === 'local' && (
<button id="bulk-reset-btn" class="flex items-center gap-1 px-4 py-2.5 rounded-full bg-amber-500/10 hover:bg-amber-500 text-amber-400 hover:text-white text-[9px] font-black uppercase tracking-widest transition-all border border-amber-500/20">
<i class="fas fa-undo"></i> Reset Gc
</button>
)}
<span class="h-4 w-px bg-white/10" />
<button id="bulk-cancel-btn" class="px-4 py-2.5 rounded-full bg-white/5 hover:bg-white/10 text-gray-400 hover:text-white text-[9px] font-black uppercase tracking-widest transition-all border border-white/5">Hy</button>
</div>
</div>
</AdminLayout>
<script>
function init() {
const { txamodal, txatoast } = (window as any);
if (!txatoast) return;
// 1. Single Action Buttons
// 1.1 Delete Single
document.querySelectorAll('.delete-single-btn').forEach(btn => {
const b = btn as HTMLButtonElement;
b.onclick = () => {
const slug = b.getAttribute('data-slug');
txamodal.show({
title: 'Xác nhận ẩn phim',
message: `Bạn có chắc chắn muốn ẩn phim <strong>${slug}</strong> khỏi hệ thống? Người xem sẽ không thể tìm thấy hoặc truy cập phim này nữa.`,
type: 'danger',
confirmText: 'Ẩn phim',
onConfirm: async () => {
try {
const res = await fetch('/api/admin/movie/delete', {
method: 'POST',
body: JSON.stringify({ slug })
});
const data = await res.json();
if (res.ok) {
txatoast.success(data.message);
setTimeout(() => window.location.reload(), 1000);
return true;
} else {
txatoast.error(data.error);
return false;
}
} catch (e) {
txatoast.error('Gặp lỗi kết nối!');
return false;
}
}
});
};
});
// 1.2 Restore Single
document.querySelectorAll('.restore-single-btn').forEach(btn => {
const b = btn as HTMLButtonElement;
b.onclick = () => {
const slug = b.getAttribute('data-slug');
txamodal.show({
title: 'Khôi phục hiển thị phim',
message: `Bạn muốn khôi phục hiển thị cho phim <strong>${slug}</strong>? Phim sẽ xuất hiện trở lại trên toàn trang web.`,
type: 'success',
confirmText: 'Khôi phục',
onConfirm: async () => {
try {
const res = await fetch('/api/admin/movie/restore', {
method: 'POST',
body: JSON.stringify({ slug })
});
const data = await res.json();
if (res.ok) {
txatoast.success(data.message);
setTimeout(() => window.location.reload(), 1000);
return true;
} else {
txatoast.error(data.error);
return false;
}
} catch (e) {
txatoast.error('Gặp lỗi kết nối!');
return false;
}
}
});
};
});
// 1.3 Reset Single
document.querySelectorAll('.reset-single-btn').forEach(btn => {
const b = btn as HTMLButtonElement;
b.onclick = () => {
const slug = b.getAttribute('data-slug');
txamodal.show({
title: 'Đặt lại dữ liệu gốc',
message: `Bạn muốn xóa bỏ mọi chỉnh sửa nội bộ của phim <strong>${slug}</strong> và quay về sử dụng dữ liệu mặc định từ API? Việc này không thể hoàn tác.`,
type: 'warning',
confirmText: 'Đặt lại gốc',
onConfirm: async () => {
try {
const res = await fetch('/api/admin/movie/reset', {
method: 'POST',
body: JSON.stringify({ slug })
});
const data = await res.json();
if (res.ok) {
txatoast.success(data.message);
setTimeout(() => window.location.reload(), 1000);
return true;
} else {
txatoast.error(data.error);
return false;
}
} catch (e) {
txatoast.error('Gặp lỗi kết nối!');
return false;
}
}
});
};
});
// 2. Checkbox & Bulk Actions Logic
const selectAllCheckbox = document.getElementById('select-all') as HTMLInputElement;
const checkboxes = document.querySelectorAll('.movie-checkbox') as NodeListOf<HTMLInputElement>;
const bulkBar = document.getElementById('bulk-bar');
const bulkCount = document.getElementById('bulk-count');
const updateBulkBar = () => {
const checkedBoxes = Array.from(checkboxes).filter(cb => cb.checked);
const count = checkedBoxes.length;
if (count > 0 && bulkBar && bulkCount) {
bulkCount.innerText = String(count);
bulkBar.classList.remove('translate-y-32', 'opacity-0', 'pointer-events-none');
bulkBar.classList.add('translate-y-0', 'opacity-100', 'pointer-events-auto');
} else if (bulkBar) {
bulkBar.classList.add('translate-y-32', 'opacity-0', 'pointer-events-none');
bulkBar.classList.remove('translate-y-0', 'opacity-100', 'pointer-events-auto');
}
};
if (selectAllCheckbox) {
selectAllCheckbox.onchange = () => {
checkboxes.forEach(cb => {
cb.checked = selectAllCheckbox.checked;
});
updateBulkBar();
};
}
checkboxes.forEach(cb => {
cb.onchange = () => {
const checkedBoxes = Array.from(checkboxes).filter(c => c.checked);
if (selectAllCheckbox) selectAllCheckbox.checked = checkedBoxes.length === checkboxes.length;
updateBulkBar();
};
});
const getSelectedSlugs = () => {
return Array.from(checkboxes)
.filter(cb => cb.checked)
.map(cb => cb.getAttribute('data-slug') || '');
};
const executeBulkAction = async (action, value = null) => {
const slugs = getSelectedSlugs();
try {
const res = await fetch('/api/admin/movie/bulk', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ slugs, action, value })
});
const data = await res.json();
if (res.ok) {
txatoast.success(data.message);
setTimeout(() => window.location.reload(), 1000);
} else {
txatoast.error(data.error);
}
} catch (e) {
txatoast.error('Gặp lỗi kết nối!');
}
};
// 2.1 Bulk Hide (Ẩn)
const bulkHideBtn = document.getElementById('bulk-hide-btn');
if (bulkHideBtn) {
bulkHideBtn.onclick = () => {
const count = getSelectedSlugs().length;
txamodal.show({
title: 'Ẩn hàng loạt phim',
message: `Bạn có chắc chắn muốn ẩn <strong>${count}</strong> phim đã chọn khỏi trang chủ?`,
type: 'danger',
confirmText: `Ẩn ${count} phim`,
onConfirm: async () => {
await executeBulkAction('delete');
return true;
}
});
};
}
// 2.2 Bulk Show (Khôi phục hiển thị)
const bulkShowBtn = document.getElementById('bulk-show-btn');
if (bulkShowBtn) {
bulkShowBtn.onclick = () => {
const count = getSelectedSlugs().length;
txamodal.show({
title: 'Hiện hàng loạt phim',
message: `Bạn muốn khôi phục hiển thị cho <strong>${count}</strong> phim đã chọn?`,
type: 'success',
confirmText: `Khôi phục ${count} phim`,
onConfirm: async () => {
await executeBulkAction('restore');
return true;
}
});
};
}
// 2.3 Bulk Hard Delete (Xóa hẳn)
const bulkHardDeleteBtn = document.getElementById('bulk-hard-delete-btn');
if (bulkHardDeleteBtn) {
bulkHardDeleteBtn.onclick = () => {
const count = getSelectedSlugs().length;
txamodal.show({
title: 'Xóa vĩnh viễn phim hàng loạt',
message: `Bạn có chắc chắn muốn xóa vĩnh viễn <strong>${count}</strong> phim đã chọn khỏi cơ sở dữ liệu? Hành động này sẽ xóa hoàn toàn và KHÔNG THỂ khôi phục!`,
type: 'danger',
confirmText: `Xóa vĩnh viễn`,
onConfirm: async () => {
await executeBulkAction('hard-delete');
return true;
}
});
};
}
// 2.4 Quick Status Set
document.querySelectorAll('.bulk-status-btn').forEach(btn => {
const b = btn as HTMLButtonElement;
b.onclick = () => {
const status = b.getAttribute('data-status');
const count = getSelectedSlugs().length;
const statusNames = { completed: 'Hoàn thành', ongoing: 'Đang chiếu', trailer: 'Sắp chiếu' };
txamodal.show({
title: 'Cập nhật trạng thái hàng loạt',
message: `Bạn muốn cập nhật trạng thái của <strong>${count}</strong> phim đã chọn thành <strong>"${statusNames[status] || status}"</strong>?`,
type: 'warning',
confirmText: `Cập nhật`,
onConfirm: async () => {
await executeBulkAction('status', status);
return true;
}
});
};
});
// 2.5 Bulk Reset (nếu có)
const bulkResetBtn = document.getElementById('bulk-reset-btn');
if (bulkResetBtn) {
bulkResetBtn.onclick = () => {
const count = getSelectedSlugs().length;
txamodal.show({
title: 'Đặt lại gốc hàng loạt',
message: `Bạn chắc chắn muốn đặt lại dữ liệu gốc cho <strong>${count}</strong> phim đã chọn? Mọi tùy chỉnh nội bộ của các phim này sẽ bị xóa bỏ.`,
type: 'warning',
confirmText: `Đặt lại gốc`,
onConfirm: async () => {
await executeBulkAction('reset');
return true;
}
});
};
}
// 2.7 Bulk Lock (Khóa Login)
const bulkLockBtn = document.getElementById('bulk-lock-btn');
if (bulkLockBtn) {
bulkLockBtn.onclick = () => {
const count = getSelectedSlugs().length;
txamodal.show({
title: 'Yêu cầu đăng nhập hàng loạt',
message: `Bạn muốn thiết lập bắt buộc đăng nhập để xem đối với <strong>${count}</strong> phim đã chọn?`,
type: 'warning',
confirmText: `Khóa Login`,
onConfirm: async () => {
await executeBulkAction('require-login');
return true;
}
});
};
}
// 2.8 Bulk Unlock (Mở tự do)
const bulkUnlockBtn = document.getElementById('bulk-unlock-btn');
if (bulkUnlockBtn) {
bulkUnlockBtn.onclick = () => {
const count = getSelectedSlugs().length;
txamodal.show({
title: 'Mở khóa xem tự do hàng loạt',
message: `Bạn muốn bỏ yêu cầu đăng nhập đối với <strong>${count}</strong> phim đã chọn?`,
type: 'success',
confirmText: `Mở tự do`,
onConfirm: async () => {
await executeBulkAction('free-login');
return true;
}
});
};
}
// 2.6 Cancel Bulk
const bulkCancelBtn = document.getElementById('bulk-cancel-btn');
if (bulkCancelBtn) {
bulkCancelBtn.onclick = () => {
checkboxes.forEach(cb => { cb.checked = false; });
if (selectAllCheckbox) selectAllCheckbox.checked = false;
updateBulkBar();
};
}
}
document.addEventListener('astro:page-load', init);
</script>
<style>
.glass {
background: rgba(18, 20, 29, 0.4);
backdrop-filter: blur(40px) saturate(200%);
}
</style>