Spaces:
Sleeping
Sleeping
| --- | |
| import AdminLayout from '../../layouts/AdminLayout.astro'; | |
| import { getActors } from '../../lib/localDb.js'; | |
| const actors = await getActors(); | |
| --- | |
| <AdminLayout title="Quản lý Diễn viên"> | |
| <div class="mb-12 flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between"> | |
| <div> | |
| <h1 class="text-4xl font-black tracking-tighter text-white">Quản lý Diễn viên</h1> | |
| <p class="mt-2 text-gray-500">Danh sách diễn viên tham gia diễn xuất trong các bộ phim của hệ thống.</p> | |
| </div> | |
| <div class="flex flex-wrap gap-3"> | |
| <button id="sync-actors-btn" class="flex items-center gap-2 rounded-2xl bg-emerald-500/10 hover:bg-emerald-500 text-emerald-400 hover:text-white border border-emerald-500/20 px-6 py-3.5 text-xs font-black uppercase tracking-widest transition-all active:scale-95 cursor-pointer"> | |
| <i class="fas fa-sync-alt"></i> | |
| Đồng bộ từ Phim | |
| </button> | |
| <button id="create-actor-btn" class="flex items-center gap-2 rounded-2xl bg-primary px-6 py-3.5 text-xs font-black uppercase tracking-widest text-dark hover:scale-105 transition-all shadow-[0_0_20px_var(--primary)] cursor-pointer"> | |
| <i class="fas fa-plus"></i> | |
| Thêm Diễn viên mới | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Bulk Action Bar --> | |
| <div id="bulk-bar" class="hidden glass rounded-3xl p-6 border border-primary/20 mb-8 items-center justify-between"> | |
| <div class="flex items-center gap-3"> | |
| <span class="text-xs font-black uppercase tracking-widest text-primary"><i class="fas fa-check-square"></i> Đã chọn <span id="selected-count">0</span> diễn viên</span> | |
| </div> | |
| <div class="flex gap-2"> | |
| <button id="bulk-delete-btn" class="flex items-center gap-2 rounded-xl bg-red-500/10 hover:bg-red-500 text-red-400 hover:text-white border border-red-500/20 px-4 py-2.5 text-xs font-black uppercase tracking-widest transition-all active:scale-95 cursor-pointer"> | |
| <i class="fas fa-trash"></i> Xóa hàng loạt | |
| </button> | |
| </div> | |
| </div> | |
| <div class="glass rounded-[40px] p-8 md:p-10 border border-white/5 space-y-6"> | |
| <!-- Filter section --> | |
| <div class="flex items-center justify-between border-b border-white/5 pb-6"> | |
| <div class="relative w-full max-w-md"> | |
| <i class="fas fa-search absolute left-5 top-1/2 -translate-y-1/2 text-gray-500 text-sm"></i> | |
| <input type="text" id="search-input" placeholder="Tìm kiếm tên diễn viên..." class="w-full bg-[#12141d] border border-white/5 rounded-2xl pl-12 pr-5 py-3.5 text-sm text-white focus:outline-none focus:border-primary/50 transition-all" /> | |
| </div> | |
| </div> | |
| <div class="overflow-x-auto"> | |
| <table class="w-full text-left"> | |
| <thead> | |
| <tr class="border-b border-white/5"> | |
| <th class="pb-6 w-12"> | |
| <label class="relative flex items-center justify-center cursor-pointer"> | |
| <input type="checkbox" id="chk-all" class="peer sr-only" /> | |
| <div class="h-5 w-5 rounded-lg border border-white/10 bg-[#12141d] flex items-center justify-center transition-all peer-checked:bg-primary peer-checked:border-primary"> | |
| <i class="fas fa-check text-[10px] text-dark opacity-0 peer-checked:opacity-100 transition-opacity"></i> | |
| </div> | |
| </label> | |
| </th> | |
| <th class="pb-6 text-[10px] font-black uppercase tracking-widest text-gray-500">Tên Diễn viên</th> | |
| <th class="pb-6 text-right text-[10px] font-black uppercase tracking-widest text-gray-500">Thao tác</th> | |
| </tr> | |
| </thead> | |
| <tbody id="table-body" class="divide-y divide-white/5"> | |
| <!-- Rendered via Javascript --> | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| </AdminLayout> | |
| <script is:inline define:vars={{ initialActors: actors }}> | |
| let actors = initialActors || []; | |
| let searchQuery = ''; | |
| function renderTable() { | |
| const tbody = document.getElementById('table-body'); | |
| if (!tbody) return; | |
| const filtered = actors.filter(a => a.name.toLowerCase().includes(searchQuery.toLowerCase())); | |
| if (filtered.length === 0) { | |
| tbody.innerHTML = ` | |
| <tr> | |
| <td colspan="3" class="py-12 text-center text-gray-500 text-sm"> | |
| <i class="fas fa-user-friends text-3xl mb-3 block opacity-20"></i> | |
| Không tìm thấy diễn viên nào | |
| </td> | |
| </tr> | |
| `; | |
| return; | |
| } | |
| tbody.innerHTML = filtered.map(a => ` | |
| <tr class="hover:bg-white/[0.01] transition-colors group"> | |
| <td class="py-5"> | |
| <label class="relative flex items-center justify-center cursor-pointer"> | |
| <input type="checkbox" class="chk-item peer sr-only" data-name="${a.name}" /> | |
| <div class="h-5 w-5 rounded-lg border border-white/10 bg-[#12141d] flex items-center justify-center transition-all peer-checked:bg-primary peer-checked:border-primary"> | |
| <i class="fas fa-check text-[10px] text-dark opacity-0 peer-checked:opacity-100 transition-opacity"></i> | |
| </div> | |
| </label> | |
| </td> | |
| <td class="py-5 text-sm font-bold text-white">${a.name}</td> | |
| <td class="py-5 text-right"> | |
| <div class="flex justify-end gap-2"> | |
| <button class="edit-btn h-9 w-9 rounded-xl bg-white/5 hover:bg-primary/20 text-gray-400 hover:text-primary transition-all border border-white/5 flex items-center justify-center" data-name="${a.name}"> | |
| <i class="fas fa-edit text-xs"></i> | |
| </button> | |
| <button class="delete-btn h-9 w-9 rounded-xl bg-white/5 hover:bg-red-500/20 text-gray-400 hover:text-red-500 transition-all border border-white/5 flex items-center justify-center" data-name="${a.name}"> | |
| <i class="fas fa-trash text-xs"></i> | |
| </button> | |
| </div> | |
| </td> | |
| </tr> | |
| `).join(''); | |
| bindEvents(); | |
| updateBulkBar(); | |
| } | |
| function bindEvents() { | |
| // Edit Actor | |
| document.querySelectorAll('.edit-btn').forEach(btn => { | |
| btn.onclick = () => { | |
| const currentName = btn.getAttribute('data-name'); | |
| window.txamodal.show({ | |
| title: 'Sửa tên Diễn viên', | |
| message: `Thay đổi tên diễn viên: | |
| <input type="text" id="edit-act-input" value="${currentName}" class="w-full bg-[#12141d] border border-white/5 rounded-xl px-4 py-2.5 mt-2 text-sm text-white focus:outline-none focus:border-primary/50" />`, | |
| type: 'warning', | |
| confirmText: 'Lưu thay đổi', | |
| onConfirm: async () => { | |
| const newName = (document.getElementById('edit-act-input'))?.value?.trim() || ''; | |
| if (!newName) { | |
| window.txatoast.error('Tên không được để trống!'); | |
| return false; | |
| } | |
| try { | |
| const res = await fetch('/api/admin/actors', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ name: newName, originalName: currentName }) | |
| }); | |
| const data = await res.json(); | |
| if (res.ok) { | |
| actors = data.list; | |
| renderTable(); | |
| window.txatoast.success(data.message); | |
| } else { | |
| window.txatoast.error(data.error); | |
| } | |
| } catch(e) { | |
| window.txatoast.error('Lỗi kết nối máy chủ!'); | |
| } | |
| return true; | |
| } | |
| }); | |
| }; | |
| }); | |
| // Delete Actor | |
| document.querySelectorAll('.delete-btn').forEach(btn => { | |
| btn.onclick = () => { | |
| const name = btn.getAttribute('data-name'); | |
| window.txamodal.show({ | |
| title: 'Xác nhận xóa Diễn viên', | |
| message: `Bạn có chắc chắn muốn xóa Diễn viên <b>${name}</b> khỏi hệ thống?`, | |
| type: 'error', | |
| confirmText: 'Xóa ngay', | |
| onConfirm: async () => { | |
| try { | |
| const res = await fetch('/api/admin/actors', { | |
| method: 'DELETE', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ name }) | |
| }); | |
| const data = await res.json(); | |
| if (res.ok) { | |
| actors = data.list; | |
| renderTable(); | |
| window.txatoast.success(data.message); | |
| } else { | |
| window.txatoast.error(data.error); | |
| } | |
| } catch(e) { | |
| window.txatoast.error('Lỗi kết nối máy chủ!'); | |
| } | |
| return true; | |
| } | |
| }); | |
| }; | |
| }); | |
| document.querySelectorAll('.chk-item').forEach(chk => { | |
| chk.onchange = () => { | |
| updateBulkBar(); | |
| }; | |
| }); | |
| } | |
| function updateBulkBar() { | |
| const chkAll = document.getElementById('chk-all'); | |
| const checked = document.querySelectorAll('.chk-item:checked'); | |
| const totalCount = document.querySelectorAll('.chk-item').length; | |
| const bulkBar = document.getElementById('bulk-bar'); | |
| const selectedCount = document.getElementById('selected-count'); | |
| if (chkAll) { | |
| chkAll.checked = totalCount > 0 && checked.length === totalCount; | |
| } | |
| if (checked.length > 0) { | |
| if (bulkBar) { | |
| bulkBar.style.display = 'flex'; | |
| } | |
| if (selectedCount) selectedCount.textContent = checked.length; | |
| } else { | |
| if (bulkBar) { | |
| bulkBar.style.display = 'none'; | |
| } | |
| } | |
| } | |
| function init() { | |
| const searchInput = document.getElementById('search-input'); | |
| if (searchInput) { | |
| searchInput.oninput = (e) => { | |
| searchQuery = e.target.value.trim(); | |
| renderTable(); | |
| }; | |
| } | |
| const chkAll = document.getElementById('chk-all'); | |
| if (chkAll) { | |
| chkAll.onchange = () => { | |
| document.querySelectorAll('.chk-item').forEach(chk => { | |
| chk.checked = chkAll.checked; | |
| }); | |
| updateBulkBar(); | |
| }; | |
| } | |
| // Create Actor | |
| const createBtn = document.getElementById('create-actor-btn'); | |
| if (createBtn) { | |
| createBtn.onclick = () => { | |
| window.txamodal.show({ | |
| title: 'Thêm Diễn viên mới', | |
| message: `Nhập tên diễn viên mới: | |
| <input type="text" id="new-act-input" placeholder="Ví dụ: Châu Tinh Trì" class="w-full bg-[#12141d] border border-white/5 rounded-xl px-4 py-2.5 mt-2 text-sm text-white focus:outline-none focus:border-primary/50" />`, | |
| type: 'success', | |
| confirmText: 'Tạo diễn viên', | |
| onConfirm: async () => { | |
| const name = (document.getElementById('new-act-input'))?.value?.trim() || ''; | |
| if (!name) { | |
| window.txatoast.error('Tên không được để trống!'); | |
| return false; | |
| } | |
| try { | |
| const res = await fetch('/api/admin/actors', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ name }) | |
| }); | |
| const data = await res.json(); | |
| if (res.ok) { | |
| actors = data.list; | |
| renderTable(); | |
| window.txatoast.success(data.message); | |
| } else { | |
| window.txatoast.error(data.error); | |
| } | |
| } catch(e) { | |
| window.txatoast.error('Lỗi kết nối máy chủ!'); | |
| } | |
| return true; | |
| } | |
| }); | |
| }; | |
| } | |
| // Sync Actors | |
| const syncBtn = document.getElementById('sync-actors-btn'); | |
| if (syncBtn) { | |
| syncBtn.onclick = async () => { | |
| window.txatoast.info('Đang đồng bộ diễn viên từ kho phim...'); | |
| try { | |
| const res = await fetch('/api/admin/actors', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ sync: true }) | |
| }); | |
| const data = await res.json(); | |
| if (res.ok) { | |
| actors = data.list; | |
| renderTable(); | |
| window.txamodal.show({ | |
| title: 'Đồng bộ diễn viên thành công', | |
| message: data.message, | |
| type: 'success' | |
| }); | |
| } else { | |
| window.txatoast.error(data.error); | |
| } | |
| } catch(e) { | |
| window.txatoast.error('Lỗi đồng bộ diễn viên!'); | |
| } | |
| }; | |
| } | |
| // Bulk Delete | |
| const bulkDeleteBtn = document.getElementById('bulk-delete-btn'); | |
| if (bulkDeleteBtn) { | |
| bulkDeleteBtn.onclick = () => { | |
| const checked = document.querySelectorAll('.chk-item:checked'); | |
| const names = Array.from(checked).map(c => c.getAttribute('data-name')); | |
| window.txamodal.show({ | |
| title: 'Xác nhận xóa hàng loạt', | |
| message: `Bạn có chắc chắn muốn xóa <b>${names.length}</b> diễn viên đã chọn?`, | |
| type: 'error', | |
| confirmText: 'Xóa tất cả', | |
| onConfirm: async () => { | |
| try { | |
| const res = await fetch('/api/admin/actors', { | |
| method: 'DELETE', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ names }) | |
| }); | |
| const data = await res.json(); | |
| if (res.ok) { | |
| actors = data.list; | |
| renderTable(); | |
| window.txatoast.success(data.message); | |
| } else { | |
| window.txatoast.error(data.error); | |
| } | |
| } catch(e) { | |
| window.txatoast.error('Lỗi kết nối máy chủ!'); | |
| } | |
| return true; | |
| } | |
| }); | |
| }; | |
| } | |
| renderTable(); | |
| } | |
| document.addEventListener('astro:page-load', init); | |
| </script> | |
| <style> | |
| .glass { | |
| background: rgba(18, 20, 29, 0.4); | |
| backdrop-filter: blur(40px) saturate(200%); | |
| } | |
| </style> | |