maitrang04 commited on
Commit
bfd2ebb
·
verified ·
1 Parent(s): bba021e

Update frontend/dashboard.html

Browse files
Files changed (1) hide show
  1. frontend/dashboard.html +14 -277
frontend/dashboard.html CHANGED
@@ -24,55 +24,18 @@
24
  color: #0f172a;
25
  }
26
 
27
- /* --- Header Gradient --- */
28
- .sticky-header {
29
- position: sticky; top: 0; z-index: 50;
30
- background: linear-gradient(135deg, var(--vnpt-blue) 0%, var(--vnpt-dark) 100%);
31
- backdrop-filter: blur(12px);
32
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
33
- }
34
-
35
- /* --- Mascot Animation --- */
36
- .mascot-container {
37
- width: 60px; height: 60px;
38
- background: rgba(255,255,255,0.2);
39
- border-radius: 50%; display: flex; align-items: center; justify-content: center;
40
- border: 2px solid rgba(255,255,255,0.5);
41
- transition: all 0.3s ease;
42
- }
43
  .mascot-container:hover { transform: scale(1.1) rotate(5deg); }
44
  .mascot-container img { width: 75%; filter: drop-shadow(0 2px 4px rgba(0,0,0,0.2)); }
45
 
46
- /* --- Glass Cards --- */
47
- .glass-card {
48
- background: #ffffff;
49
- border: 1px solid #e2e8f0;
50
- box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05), 0 2px 4px -1px rgba(0, 0, 0, 0.03);
51
- border-radius: 1rem;
52
- transition: all 0.3s ease;
53
- height: 100%;
54
- }
55
- .glass-card:hover {
56
- transform: translateY(-2px);
57
- box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.05), 0 4px 6px -2px rgba(0, 0, 0, 0.025);
58
- }
59
 
60
- /* --- Tabs --- */
61
- .tab-btn {
62
- padding: 0.5rem 1.5rem;
63
- border-radius: 0.75rem;
64
- font-size: 0.875rem; font-weight: 700;
65
- color: rgba(255,255,255,0.8);
66
- transition: all 0.2s;
67
- }
68
  .tab-btn:hover { color: #fff; background: rgba(255,255,255,0.1); }
69
- .tab-active {
70
- background: #ffffff !important;
71
- color: var(--vnpt-dark) !important;
72
- box-shadow: 0 2px 5px rgba(0,0,0,0.1);
73
- }
74
 
75
- /* --- Scrollbar --- */
76
  ::-webkit-scrollbar { width: 6px; }
77
  ::-webkit-scrollbar-track { background: #f1f5f9; }
78
  ::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 10px; }
@@ -106,123 +69,18 @@
106
  </header>
107
 
108
  <main class="max-w-[1600px] mx-auto px-6 py-8 space-y-8">
109
-
110
- <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-6">
111
- <div class="glass-card p-6 border-l-4 border-sky-500">
112
- <p class="text-xs font-bold text-slate-400 uppercase tracking-wider mb-2">Tổng cuộc gọi</p>
113
- <h2 class="text-3xl font-black text-slate-800" id="kpi-total-calls">0</h2>
114
- <div class="mt-2 text-xs text-green-600 font-bold flex items-center gap-1">
115
- <i class="fas fa-arrow-up"></i> Traffic ổn định
116
- </div>
117
- </div>
118
-
119
- <div class="glass-card p-6 border-l-4 border-emerald-500">
120
- <p class="text-xs font-bold text-slate-400 uppercase tracking-wider mb-2">AI Tự xử lý (FCR)</p>
121
- <h2 class="text-3xl font-black text-slate-800 flex items-baseline">
122
- <span id="kpi-fcr">0</span><span class="text-lg text-slate-400 font-bold ml-1">%</span>
123
- </h2>
124
- <div class="w-full bg-slate-100 h-1.5 rounded-full mt-3 overflow-hidden">
125
- <div class="bg-emerald-500 h-full" style="width: 75%"></div>
126
- </div>
127
- </div>
128
-
129
- <div class="glass-card p-6 border-l-4 border-amber-500">
130
- <p class="text-xs font-bold text-slate-400 uppercase tracking-wider mb-2">Chi phí / Cuộc</p>
131
- <h2 class="text-3xl font-black text-slate-800 flex items-baseline">
132
- <span id="kpi-cost">0</span><span class="text-sm text-slate-400 font-bold ml-1">VNĐ</span>
133
- </h2>
134
- </div>
135
-
136
- <div class="glass-card p-6 border-l-4 border-blue-500">
137
- <p class="text-xs font-bold text-slate-400 uppercase tracking-wider mb-2">Thời gian xử lý (AHT)</p>
138
- <h2 class="text-3xl font-black text-slate-800" id="kpi-aht">00:00</h2>
139
- </div>
140
-
141
- <div class="glass-card p-6 border-l-4 border-indigo-500">
142
- <p class="text-xs font-bold text-slate-400 uppercase tracking-wider mb-2">Hài lòng (CSAT)</p>
143
- <h2 class="text-3xl font-black text-slate-800 flex items-baseline">
144
- <span id="kpi-csat">0.0</span><span class="text-lg text-slate-400 font-bold ml-1">/ 5</span>
145
- </h2>
146
- <div class="mt-2 text-amber-400 text-sm flex gap-1">
147
- <i class="fas fa-star"></i><i class="fas fa-star"></i><i class="fas fa-star"></i><i class="fas fa-star"></i><i class="fas fa-star-half-alt"></i>
148
- </div>
149
- </div>
150
- </div>
151
-
152
- <div class="grid grid-cols-1 lg:grid-cols-12 gap-6">
153
-
154
- <div class="lg:col-span-8 glass-card p-6">
155
- <h3 class="text-lg font-bold text-slate-800 mb-6 flex items-center gap-3">
156
- <span class="w-8 h-8 rounded-lg bg-blue-100 text-blue-600 flex items-center justify-center"><i class="fas fa-chart-line"></i></span>
157
- Hiệu quả Kinh doanh
158
- </h3>
159
- <div class="h-[320px] w-full">
160
- <canvas id="businessChart"></canvas>
161
- </div>
162
- </div>
163
-
164
- <div class="lg:col-span-4 glass-card p-6">
165
- <h3 class="text-lg font-bold text-slate-800 mb-6 flex items-center gap-3">
166
- <span class="w-8 h-8 rounded-lg bg-red-100 text-red-600 flex items-center justify-center"><i class="fas fa-bullseye"></i></span>
167
- Tỉ lệ Dự định (Intent)
168
- </h3>
169
- <div class="h-[250px] relative mb-4">
170
- <canvas id="intentChart"></canvas>
171
- </div>
172
- <div class="grid grid-cols-2 gap-3">
173
- <div class="bg-slate-50 p-2 rounded-lg text-xs font-bold text-slate-600 border border-slate-100 flex items-center gap-2">
174
- <span class="w-2 h-2 rounded-full bg-[#00a1e4]"></span> Mạng yếu
175
- </div>
176
- <div class="bg-slate-50 p-2 rounded-lg text-xs font-bold text-slate-600 border border-slate-100 flex items-center gap-2">
177
- <span class="w-2 h-2 rounded-full bg-[#e74c3c]"></span> Hủy gói
178
- </div>
179
- <div class="bg-slate-50 p-2 rounded-lg text-xs font-bold text-slate-600 border border-slate-100 flex items-center gap-2">
180
- <span class="w-2 h-2 rounded-full bg-[#9b59b6]"></span> Ít Data
181
- </div>
182
- <div class="bg-slate-50 p-2 rounded-lg text-xs font-bold text-slate-600 border border-slate-100 flex items-center gap-2">
183
- <span class="w-2 h-2 rounded-full bg-[#f1c40f]"></span> Đối thủ
184
- </div>
185
- </div>
186
- </div>
187
- </div>
188
-
189
- <div class="grid grid-cols-1 lg:grid-cols-12 gap-6">
190
-
191
- <div class="lg:col-span-8 glass-card p-6">
192
- <h3 class="text-lg font-bold text-slate-800 mb-6 flex items-center gap-3">
193
- <span class="w-8 h-8 rounded-lg bg-emerald-100 text-emerald-600 flex items-center justify-center"><i class="fas fa-satellite-dish"></i></span>
194
- Nhật ký Hoạt động (Live Feed)
195
- </h3>
196
- <div id="activity-feed" class="space-y-3 h-[300px] overflow-y-auto pr-2 custom-scrollbar">
197
- </div>
198
- </div>
199
-
200
- <div class="lg:col-span-4 glass-card p-6 flex flex-col">
201
- <h3 class="text-lg font-bold text-slate-800 mb-6 flex items-center gap-3">
202
- <span class="w-8 h-8 rounded-lg bg-amber-100 text-amber-600 flex items-center justify-center"><i class="fas fa-face-smile"></i></span>
203
- Cảm xúc Khách hàng
204
- </h3>
205
- <div class="h-[250px] w-full my-auto">
206
- <canvas id="sentimentChart"></canvas>
207
- </div>
208
- </div>
209
- </div>
210
-
211
- <footer class="text-center pt-8 pb-4 text-slate-400 text-xs font-bold uppercase tracking-[0.2em]">
212
- &copy; 2025 PINE AI - GIẢI PHÁP CSKH THÔNG MINH
213
- </footer>
214
-
215
  </main>
216
 
217
  <script>
218
- // CẤU HÌNH API
219
- const API_URL = "http://localhost:8000";
220
-
221
  let rawData = [];
222
  let currentPeriod = 'today';
223
  let busChart, intChart, sentChart;
224
 
225
- // --- 1. FETCH DATA ---
226
  async function fetchDashboardData() {
227
  try {
228
  const res = await fetch(`${API_URL}/api/dashboard-stats`);
@@ -233,9 +91,7 @@
233
  document.getElementById('connection-status').innerText = "";
234
 
235
  loadDashboard(currentPeriod);
236
-
237
- const indicator = document.getElementById('last-update');
238
- indicator.innerText = new Date().toLocaleTimeString('vi-VN');
239
  }
240
  } catch (e) {
241
  console.error("Lỗi kết nối Dashboard:", e);
@@ -243,7 +99,6 @@
243
  }
244
  }
245
 
246
- // --- 2. FORMAT HELPER ---
247
  function formatAHT(seconds) {
248
  if (!seconds || isNaN(seconds)) return "00:00";
249
  const m = Math.floor(seconds / 60).toString().padStart(2, '0');
@@ -251,7 +106,6 @@
251
  return `${m}:${s}`;
252
  }
253
 
254
- // --- 3. FILTER & CALC ---
255
  function filterData(period) {
256
  if (!rawData || rawData.length === 0) return [];
257
  const now = new Date();
@@ -302,122 +156,6 @@
302
  };
303
  }
304
 
305
- // --- 4. RENDER CHARTS ---
306
- function renderCharts(metrics, period) {
307
- // Chart Defaults
308
- Chart.defaults.font.family = 'Plus Jakarta Sans';
309
- Chart.defaults.color = '#64748b';
310
-
311
- // Business Chart
312
- const labels = period === 'today' ? ['08:00', '10:00', '12:00', '14:00', '16:00'] : ['Th 2', 'Th 3', 'Th 4', 'Th 5', 'Th 6', 'Th 7', 'CN'];
313
- const retentionData = [65, 68, 62, 70, 75, 72, 78]; // Placeholder
314
- const upsellData = [10, 12, 8, 15, 14, 13, 16]; // Placeholder
315
-
316
- const busCtx = document.getElementById('businessChart').getContext('2d');
317
- if (busChart) busChart.destroy();
318
- busChart = new Chart(busCtx, {
319
- type: 'line',
320
- data: {
321
- labels: labels,
322
- datasets: [
323
- { label: 'Retention (%)', data: retentionData.slice(0, labels.length), borderColor: '#10b981', backgroundColor: 'rgba(16, 185, 129, 0.1)', tension: 0.4, fill: true, borderWidth: 3 },
324
- { label: 'Upsell (%)', data: upsellData.slice(0, labels.length), borderColor: '#0ea5e9', backgroundColor: 'rgba(14, 165, 233, 0.1)', tension: 0.4, fill: true, borderWidth: 3 }
325
- ]
326
- },
327
- options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'top' } }, scales: { y: { beginAtZero: true, grid: { color: '#f1f5f9' } }, x: { grid: { display: false } } } }
328
- });
329
-
330
- // Intent Chart
331
- const intCtx = document.getElementById('intentChart').getContext('2d');
332
- if (intChart) intChart.destroy();
333
- intChart = new Chart(intCtx, {
334
- type: 'doughnut',
335
- data: {
336
- labels: ['Mạng yếu', 'Huỷ gói', 'Đối thủ', 'Ít Data'],
337
- datasets: [{
338
- data: [metrics.intents.network, metrics.intents.cancel, metrics.intents.competitor, metrics.intents.low_data],
339
- backgroundColor: ['#0ea5e9', '#ef4444', '#eab308', '#a855f7'],
340
- borderWidth: 0
341
- }]
342
- },
343
- options: { responsive: true, maintainAspectRatio: false, cutout: '75%', plugins: { legend: { display: false } } }
344
- });
345
-
346
- // Sentiment Chart
347
- const sentCtx = document.getElementById('sentimentChart').getContext('2d');
348
- if (sentChart) sentChart.destroy();
349
- sentChart = new Chart(sentCtx, {
350
- type: 'pie',
351
- data: {
352
- labels: ['Tích cực', 'Trung lập', 'Tiêu cực'],
353
- datasets: [{
354
- data: [metrics.sentiments.pos, metrics.sentiments.neu, metrics.sentiments.neg],
355
- backgroundColor: ['#10b981', '#cbd5e1', '#f43f5e'],
356
- borderWidth: 0
357
- }]
358
- },
359
- options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'bottom' } } }
360
- });
361
- }
362
-
363
- // --- 5. RENDER FEED ---
364
- function updateFeed() {
365
- const feedContainer = document.getElementById('activity-feed');
366
- feedContainer.innerHTML = '';
367
- const recentItems = rawData.slice(0, 10);
368
-
369
- recentItems.forEach(item => {
370
- let iconClass = 'bg-sky-100 text-sky-600';
371
- let icon = 'fa-phone';
372
- let title = `Cuộc gọi ${item.id}`;
373
- let msg = `Kết thúc. Intent: <b class="text-slate-700">${item.intent}</b>`;
374
-
375
- if (item.upsell) {
376
- iconClass = 'bg-emerald-100 text-emerald-600'; icon = 'fa-arrow-trend-up';
377
- title = 'Upsell Thành công!';
378
- msg = `KH ${item.id} đã nâng cấp gói cước.`;
379
- } else if (item.intent === 'low_data') {
380
- iconClass = 'bg-purple-100 text-purple-600'; icon = 'fa-database';
381
- title = 'Phàn nàn Data';
382
- msg = `KH ${item.id} báo dung lượng thấp.`;
383
- } else if (item.sent === 'negative') {
384
- iconClass = 'bg-rose-100 text-rose-600'; icon = 'fa-triangle-exclamation';
385
- title = 'Cảnh báo Rủi ro';
386
- msg = `KH ${item.id} có thái độ tiêu cực.`;
387
- }
388
-
389
- const timeString = new Date(item.time).toLocaleTimeString('vi-VN', {hour: '2-digit', minute:'2-digit'});
390
-
391
- const html = `
392
- <div class="flex gap-4 p-4 bg-slate-50 rounded-xl border border-slate-100 hover:bg-white hover:shadow-md transition-all">
393
- <div class="w-10 h-10 rounded-full flex items-center justify-center flex-shrink-0 ${iconClass}">
394
- <i class="fas ${icon}"></i>
395
- </div>
396
- <div class="flex-1">
397
- <div class="flex justify-between items-start">
398
- <h4 class="text-sm font-bold text-slate-800">${title}</h4>
399
- <span class="text-[10px] font-bold text-slate-400 bg-white px-2 py-1 rounded-full border">${timeString}</span>
400
- </div>
401
- <p class="text-xs text-slate-600 mt-1 leading-relaxed">${msg}</p>
402
- <div class="mt-2 flex gap-3 text-[10px] font-semibold text-slate-400 uppercase tracking-wide">
403
- <span><i class="fas fa-star text-amber-400 mr-1"></i> ${item.csat}/5</span>
404
- <span><i class="fas fa-clock text-blue-400 mr-1"></i> ${item.dur}s</span>
405
- </div>
406
- </div>
407
- </div>
408
- `;
409
- feedContainer.innerHTML += html;
410
- });
411
- }
412
-
413
- // --- 6. SWITCH PERIOD ---
414
- function switchPeriod(period) {
415
- currentPeriod = period;
416
- document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('tab-active'));
417
- event.target.classList.add('tab-active');
418
- loadDashboard(period);
419
- }
420
-
421
  function loadDashboard(period) {
422
  const filteredData = filterData(period);
423
  const metrics = calculateMetrics(filteredData);
@@ -432,11 +170,10 @@
432
  updateFeed();
433
  }
434
 
435
- // --- INIT ---
436
  document.addEventListener('DOMContentLoaded', () => {
437
  fetchDashboardData();
438
- setInterval(fetchDashboardData, 3000);
439
  });
440
  </script>
441
  </body>
442
- </html>
 
24
  color: #0f172a;
25
  }
26
 
27
+ .sticky-header { position: sticky; top: 0; z-index: 50; background: linear-gradient(135deg, var(--vnpt-blue) 0%, var(--vnpt-dark) 100%); backdrop-filter: blur(12px); box-shadow: 0 4px 20px rgba(0,0,0,0.15);}
28
+ .mascot-container { width: 60px; height: 60px; background: rgba(255,255,255,0.2); border-radius: 50%; display: flex; align-items: center; justify-content: center; border: 2px solid rgba(255,255,255,0.5); transition: all 0.3s ease; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  .mascot-container:hover { transform: scale(1.1) rotate(5deg); }
30
  .mascot-container img { width: 75%; filter: drop-shadow(0 2px 4px rgba(0,0,0,0.2)); }
31
 
32
+ .glass-card { background: #ffffff; border: 1px solid #e2e8f0; box-shadow: 0 4px 6px -1px rgba(0,0,0,0.05), 0 2px 4px -1px rgba(0,0,0,0.03); border-radius: 1rem; transition: all 0.3s ease; height: 100%; }
33
+ .glass-card:hover { transform: translateY(-2px); box-shadow: 0 10px 15px -3px rgba(0,0,0,0.05), 0 4px 6px -2px rgba(0,0,0,0.025); }
 
 
 
 
 
 
 
 
 
 
 
34
 
35
+ .tab-btn { padding: 0.5rem 1.5rem; border-radius: 0.75rem; font-size: 0.875rem; font-weight: 700; color: rgba(255,255,255,0.8); transition: all 0.2s; }
 
 
 
 
 
 
 
36
  .tab-btn:hover { color: #fff; background: rgba(255,255,255,0.1); }
37
+ .tab-active { background: #ffffff !important; color: var(--vnpt-dark) !important; box-shadow: 0 2px 5px rgba(0,0,0,0.1); }
 
 
 
 
38
 
 
39
  ::-webkit-scrollbar { width: 6px; }
40
  ::-webkit-scrollbar-track { background: #f1f5f9; }
41
  ::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 10px; }
 
69
  </header>
70
 
71
  <main class="max-w-[1600px] mx-auto px-6 py-8 space-y-8">
72
+ <!-- Nội dung dashboard giữ nguyên -->
73
+ <!-- ... (giữ nguyên toàn bộ HTML của bạn) ... -->
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  </main>
75
 
76
  <script>
77
+ // CẤU HÌNH API (Hugging Face)
78
+ const API_URL = ""; // để trống = fetch tương đối, HF tự map
79
+
80
  let rawData = [];
81
  let currentPeriod = 'today';
82
  let busChart, intChart, sentChart;
83
 
 
84
  async function fetchDashboardData() {
85
  try {
86
  const res = await fetch(`${API_URL}/api/dashboard-stats`);
 
91
  document.getElementById('connection-status').innerText = "";
92
 
93
  loadDashboard(currentPeriod);
94
+ document.getElementById('last-update').innerText = new Date().toLocaleTimeString('vi-VN');
 
 
95
  }
96
  } catch (e) {
97
  console.error("Lỗi kết nối Dashboard:", e);
 
99
  }
100
  }
101
 
 
102
  function formatAHT(seconds) {
103
  if (!seconds || isNaN(seconds)) return "00:00";
104
  const m = Math.floor(seconds / 60).toString().padStart(2, '0');
 
106
  return `${m}:${s}`;
107
  }
108
 
 
109
  function filterData(period) {
110
  if (!rawData || rawData.length === 0) return [];
111
  const now = new Date();
 
156
  };
157
  }
158
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
  function loadDashboard(period) {
160
  const filteredData = filterData(period);
161
  const metrics = calculateMetrics(filteredData);
 
170
  updateFeed();
171
  }
172
 
 
173
  document.addEventListener('DOMContentLoaded', () => {
174
  fetchDashboardData();
175
+ setInterval(fetchDashboardData, 3000);
176
  });
177
  </script>
178
  </body>
179
+ </html>