File size: 10,202 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
204
205
---
import AdminLayout from '../../layouts/AdminLayout.astro';
import { txasupabase } from '../../lib/txasupabase.js';

// Fetch real statistics from Supabase
const localMovies = await txasupabase.getLocalMovies();
const totalMovies = Object.keys(localMovies).length;

const users = await txasupabase.getUsers();
const totalUsers = users.length;

// Fetch real count for comments
const { count: commentsCount } = await txasupabase.supabase
  .from('comments')
  .select('*', { count: 'exact', head: true });

// Fetch real count for email logs (báo lỗi/gửi mail)
const { count: emailLogsCount } = await txasupabase.supabase
  .from('email_logs')
  .select('*', { count: 'exact', head: true });

const stats = [
  { label: 'Tổng số phim (Cục bộ)', value: String(totalMovies), icon: 'fas fa-film', color: 'text-blue-500' },
  { label: 'Tổng thành viên', value: String(totalUsers), icon: 'fas fa-user-plus', color: 'text-green-500' },
  { label: 'Tổng bình luận', value: String(commentsCount || 0), icon: 'fas fa-comments', color: 'text-purple-500' },
  { label: 'Lịch sử gửi Email', value: String(emailLogsCount || 0), icon: 'fas fa-envelope-open-text', color: 'text-red-500' },
];

// Phim mới cập nhật từ Database cục bộ
const newestMovies = Object.values(localMovies)
  .sort((a: any, b: any) => {
    const timeA = new Date(a.movie.modified?.time || a.movie.created?.time || 0).getTime();
    const timeB = new Date(b.movie.modified?.time || b.movie.created?.time || 0).getTime();
    return timeB - timeA;
  })
  .slice(0, 5);

const formatImageUrl = (url: string) => {
  if (!url) return '/placeholder-poster.jpg';
  if (url.startsWith('http')) return url;
  const cleanUrl = url.startsWith('/') ? url : `/${url}`;
  return `https://phimimg.com${cleanUrl}`;
};
---

<AdminLayout title="Bảng điều khiển">
  <div class="mb-12 flex items-center justify-between">
    <div>
      <h1 class="text-4xl font-black tracking-tighter text-white">Bảng điều khiển</h1>
      <p class="mt-2 text-gray-500">Chào mừng bạn trở lại, hệ thống đang hoạt động ổn định.</p>
    </div>
    <div class="flex gap-4">
       <button class="flex items-center gap-2 rounded-2xl bg-white/5 px-6 py-3 text-xs font-black uppercase tracking-widest text-white hover:bg-white/10 transition-all border border-white/5" data-txatooltip="Tải báo cáo chi tiết">
          <i class="fas fa-download"></i>
          Xuất báo cáo
       </button>
       <button id="add-movie-btn" class="flex items-center gap-2 rounded-2xl bg-primary px-6 py-3 text-xs font-black uppercase tracking-widest text-dark hover:scale-105 transition-all shadow-[0_0_20px_var(--primary)]" data-txatooltip="Thêm phim vào hệ thống">
          <i class="fas fa-plus"></i>
          Thêm phim mới
       </button>
    </div>
  </div>

  <div class="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-4">
    {stats.map(stat => (
      <div class="glass rounded-[32px] p-8 border border-white/5 relative overflow-hidden group">
        <div class="relative z-10 flex flex-col gap-4">
           <div class={`h-12 w-12 rounded-2xl bg-white/5 flex items-center justify-center ${stat.color} text-xl group-hover:scale-110 transition-transform`}>
              <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-3xl font-black text-white mt-1 txa-format-number" data-value={stat.value.replace(/,/g, '')}>{stat.value}</h3>
           </div>
        </div>
        <!-- Decoration -->
        <div class={`absolute -bottom-4 -right-4 h-24 w-24 rounded-full bg-white/5 blur-3xl ${stat.color} opacity-20`}></div>
      </div>
    ))}
  </div>

  <div class="mt-12 grid grid-cols-1 gap-12 lg:grid-cols-3">
     <div class="lg:col-span-2 glass rounded-[40px] p-10 border border-white/5">
        <div class="mb-8 flex items-center justify-between">
           <h2 class="text-xl font-black tracking-tighter text-white">Phim mới cập nhật</h2>
           <a href="/admin/phim" class="text-[10px] font-black uppercase tracking-widest text-primary hover:underline">Xem tất cả</a>
        </div>
        <div class="overflow-x-auto">
           <table class="w-full text-left">
              <thead>
                 <tr class="border-b border-white/5">
                    <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">Ngày cập nhật</th>
                    <th class="pb-6 text-[10px] font-black uppercase tracking-widest text-gray-500">Lượt xem</th>
                    <th class="pb-6 text-[10px] font-black uppercase tracking-widest text-gray-500">Hành động</th>
                 </tr>
              </thead>
              <tbody class="divide-y divide-white/5">
                 {newestMovies.length > 0 ? newestMovies.map((m: any) => {
                    const movie = m.movie;
                    const poster = formatImageUrl(movie.poster_url || movie.thumb_url);
                    const genres = movie.category ? movie.category.map((c: any) => c.name).join(', ') : 'Chưa phân loại';
                    const modifiedTime = movie.modified?.time || movie.created?.time || new Date().toISOString();
                    const views = movie.view || 0;
                    return (
                       <tr>
                          <td class="py-6">
                             <div class="flex items-center gap-4">
                                <div class="h-14 w-10 overflow-hidden rounded-xl bg-white/5 border border-white/10 shrink-0">
                                   <img src={poster} alt={movie.name} class="h-full w-full object-cover" />
                                </div>
                                <div class="flex flex-col min-w-0">
                                   <span class="text-sm font-bold text-white truncate">{movie.name}</span>
                                   <span class="text-[10px] font-medium text-gray-500 uppercase tracking-widest truncate">{genres}</span>
                                </div>
                             </div>
                          </td>
                          <td class="py-6 text-xs text-gray-400 txa-format-date" data-value={modifiedTime}></td>
                          <td class="py-6 text-xs font-bold text-white txa-format-short" data-value={views}></td>
                          <td class="py-6">
                             <div class="flex gap-2">
                                <a href={`/admin/phim/chinh-sua?slug=${movie.slug}`} class="h-8 w-8 rounded-lg bg-white/5 flex items-center justify-center text-gray-500 hover:text-primary transition-colors border border-white/5" data-txatooltip="Chỉnh sửa phim">
                                   <i class="fas fa-edit text-[10px]"></i>
                                </a>
                                <button class="h-8 w-8 rounded-lg bg-white/5 flex items-center justify-center text-gray-500 hover:text-red-500 transition-colors border border-white/5" data-txatooltip="Xóa phim" onclick={`window.location.href='/admin/phim?keyword=${movie.slug}'`}>
                                   <i class="fas fa-trash text-[10px]"></i>
                                </button>
                             </div>
                          </td>
                       </tr>
                    );
                 }) : (
                    <tr>
                       <td colspan="4" class="py-12 text-center text-gray-500 text-xs font-bold">Chưa có phim nào trong cơ sở dữ liệu cục bộ.</td>
                    </tr>
                 )}
              </tbody>
           </table>
        </div>
     </div>

     <div class="glass rounded-[40px] p-10 border border-white/5">
        <h2 class="mb-8 text-xl font-black tracking-tighter text-white">Báo cáo gần đây</h2>
        <div class="space-y-6">
           {[1,2,3,4].map(i => (
              <div class="flex gap-4 p-4 rounded-3xl bg-white/5 border border-white/5 group cursor-pointer hover:bg-white/10 transition-colors" data-txatooltip="Xem chi tiết lỗi">
                 <div class="h-10 w-10 rounded-2xl bg-red-500/10 flex items-center justify-center text-red-500 group-hover:scale-110 transition-transform">
                    <i class="fas fa-bug"></i>
                 </div>
                 <div class="flex flex-col">
                    <span class="text-xs font-bold text-white">Lỗi không xem được tập {i}</span>
                    <span class="text-[10px] font-medium text-gray-500 uppercase tracking-widest mt-1">Phim: Trục Ngọc</span>
                 </div>
              </div>
           ))}
        </div>
     </div>
  </div>
</AdminLayout>

<script>
  function formatData() {
    const { txaformat, initTxaTooltips, txamodal, txatoast } = (window as any);
    if (!txaformat) return;

    // Format numbers
    document.querySelectorAll('.txa-format-number').forEach(el => {
      const val = parseInt(el.getAttribute('data-value') || '0');
      el.textContent = txaformat.number(val);
    });

    // Format short numbers
    document.querySelectorAll('.txa-format-short').forEach(el => {
      const val = parseInt(el.getAttribute('data-value') || '0');
      el.textContent = txaformat.shortNumber(val);
    });

    // Format dates
    document.querySelectorAll('.txa-format-date').forEach(el => {
      const val = el.getAttribute('data-value');
      el.textContent = txaformat.dateTime(val).split(',')[0]; // Just date
    });

    // Init Tooltips
    if (initTxaTooltips) initTxaTooltips();

    // Add Movie Action
    const addBtn = document.getElementById('add-movie-btn');
    if (addBtn) {
       addBtn.onclick = () => {
          window.location.href = '/admin/phim/them-moi';
       };
    }
  }

  document.addEventListener('astro:page-load', formatData);
</script>

<style>
  .glass {
    background: rgba(18, 20, 29, 0.4);
    backdrop-filter: blur(40px) saturate(200%);
  }
</style>