190 lines
8.8 KiB
HTML
190 lines
8.8 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru" data-theme="auto">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||
<title>Топ-10 заказов B2C | Казахтелеком</title>
|
||
<link rel="stylesheet" href="design-system/kt-ai-fonts.css">
|
||
<link rel="stylesheet" href="design-system/kt-ai-tokens.css">
|
||
<link rel="stylesheet" href="design-system/kt-ai-components.css">
|
||
<link rel="stylesheet" href="design-system/kt-ai-page.css">
|
||
<style>
|
||
.dashboard-wrap { padding: var(--kt-ai-space-6) 0; }
|
||
.kpi-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: var(--kt-ai-space-4); margin-bottom: var(--kt-ai-space-8); }
|
||
.kpi-card { background: var(--kt-ai-card-bg); border: 1px solid var(--kt-ai-card-border); border-radius: var(--kt-ai-radius-xl); padding: var(--kt-ai-space-5); }
|
||
.kpi-label { font-size: var(--kt-ai-text-sm); color: var(--kt-ai-fg-muted); margin-bottom: var(--kt-ai-space-2); }
|
||
.kpi-value { font-size: var(--kt-ai-text-2xl); font-weight: var(--kt-ai-weight-bold); color: var(--kt-ai-fg); }
|
||
.table-container { overflow-x: auto; border: 1px solid var(--kt-ai-card-border); border-radius: var(--kt-ai-radius-xl); }
|
||
table { width: 100%; border-collapse: collapse; background: var(--kt-ai-card-bg); }
|
||
th { text-align: left; padding: var(--kt-ai-space-3) var(--kt-ai-space-4); font-size: var(--kt-ai-text-sm); color: var(--kt-ai-fg-muted); border-bottom: 1px solid var(--kt-ai-divider); }
|
||
td { padding: var(--kt-ai-space-4); border-bottom: 1px solid var(--kt-ai-divider); font-size: var(--kt-ai-text-sm); }
|
||
tr:last-child td { border-bottom: none; }
|
||
tr:hover td { background: var(--kt-ai-bg-soft); }
|
||
.amount { font-weight: var(--kt-ai-weight-semibold); color: var(--kt-ai-primary); }
|
||
.last-update { margin-top: var(--kt-ai-space-4); font-size: var(--kt-ai-text-sm); color: var(--kt-ai-fg-muted); }
|
||
.refresh-btn { margin-left: var(--kt-ai-space-3); }
|
||
.status-chip { display: inline-block; padding: 2px 10px; border-radius: var(--kt-ai-radius-full); font-size: var(--kt-ai-text-xs); }
|
||
.status-new { background: rgba(0, 184, 204, 0.15); color: var(--kt-ai-primary); }
|
||
.loading { text-align: center; padding: var(--kt-ai-space-12); color: var(--kt-ai-fg-muted); }
|
||
.error-state { text-align: center; padding: var(--kt-ai-space-12); color: var(--kt-ai-fg-muted); }
|
||
.error-state h3 { margin-bottom: var(--kt-ai-space-2); color: var(--kt-ai-fg); }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<header class="kt-ai-pagehead">
|
||
<div class="kt-ai-wrap kt-ai-pagehead-in">
|
||
<strong>Дашборд B2C</strong>
|
||
<span class="kt-ai-spacer"></span>
|
||
<button class="kt-ai-btn" data-variant="ghost" onclick="refreshData()">⟳ Обновить</button>
|
||
</div>
|
||
</header>
|
||
|
||
<main class="kt-ai-wrap dashboard-wrap">
|
||
<section>
|
||
<h1 style="margin-bottom: var(--kt-ai-space-2)">Топ-10 заказов B2C за сегодня</h1>
|
||
<p class="kt-ai-hero-sub" style="margin-bottom: var(--kt-ai-space-6)">Самые крупные заказы Казахтелекома по сумме</p>
|
||
|
||
<div class="kpi-grid" id="kpiGrid">
|
||
<div class="kpi-card">
|
||
<div class="kpi-label">Всего заказов B2C</div>
|
||
<div class="kpi-value" id="totalOrders">—</div>
|
||
</div>
|
||
<div class="kpi-card">
|
||
<div class="kpi-label">Сумма топ-10</div>
|
||
<div class="kpi-value" id="top10Sum">—</div>
|
||
</div>
|
||
<div class="kpi-card">
|
||
<div class="kpi-label">Средний чек</div>
|
||
<div class="kpi-value" id="avgCheck">—</div>
|
||
</div>
|
||
<div class="kpi-card">
|
||
<div class="kpi-label">Лидер региона</div>
|
||
<div class="kpi-value" id="topRegion">—</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="table-container">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>#</th>
|
||
<th>Клиент</th>
|
||
<th>Регион</th>
|
||
<th>Услуга</th>
|
||
<th>Статус</th>
|
||
<th>Сумма (₸)</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="ordersTable">
|
||
<tr><td colspan="6" class="loading">Загрузка данных…</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="last-update">
|
||
Последнее обновление: <span id="lastUpdate">—</span>
|
||
<button class="kt-ai-btn" data-variant="primary" data-size="sm" onclick="refreshData()">Обновить сейчас</button>
|
||
</div>
|
||
</section>
|
||
</main>
|
||
|
||
<footer class="kt-ai-wrap kt-ai-pagefoot">
|
||
<span>© 2026 Казахтелеком CRM</span>
|
||
<span class="kt-ai-spacer"></span>
|
||
<span>Дашборд B2C</span>
|
||
</footer>
|
||
|
||
<script>
|
||
let ordersData = [];
|
||
let lastUpdateTime = null;
|
||
|
||
// Мок-данные для демонстрации (заменятся на данные от Alem при подключении)
|
||
const mockOrders = [
|
||
{ id: 1, client: "ТОО \"Digital Solutions\"", region: "Алматы", service: "Выделенная линия 100 Мбит", status: "новый", amount: 2850000 },
|
||
{ id: 2, client: "ИП Nazarbaev", region: "Астана", service: "IP-телефония 50 номеров", status: "новый", amount: 1920000 },
|
||
{ id: 3, client: "ТОО \"KazStroyGroup\"", region: "Караганда", service: "Видеонаблюдение 24 камеры", status: "новый", amount: 1650000 },
|
||
{ id: 4, client: "АО \"AlmatyTrade\"", region: "Алматы", service: "Облачное хранилище 5ТБ", status: "новый", amount: 1420000 },
|
||
{ id: 5, client: "ТОО \"NorthOil\"", region: "Атырау", service: "Выделенная линия 50 Мбит", status: "новый", amount: 1180000 },
|
||
{ id: 6, client: "ИП Seitov", region: "Шымкент", service: "Охранная сигнализация", status: "новый", amount: 980000 },
|
||
{ id: 7, client: "ТОО \"EurasiaLogistics\"", region: "Актобе", service: "Видеоконференцсвязь", status: "новый", amount: 875000 },
|
||
{ id: 8, client: "ТОО \"BaumerKazakhstan\"", region: "Астана", service: "Доступ в интернет 200 Мбит", status: "новый", amount: 720000 },
|
||
{ id: 9, client: "ИП Omarova", region: "Павлодар", service: "IP-телефония 20 номеров", status: "новый", amount: 650000 },
|
||
{ id: 10, client: "ТОО \"SemeyStroy\"", region: "Семей", service: "Локальная сеть под ключ", status: "новый", amount: 580000 }
|
||
];
|
||
|
||
function formatAmount(amount) {
|
||
return amount.toString().replace(/\B(?=(\d{3})+(?!\d))/g, " ");
|
||
}
|
||
|
||
function renderTable(orders) {
|
||
const tbody = document.getElementById('ordersTable');
|
||
tbody.innerHTML = orders.map((order, index) => `
|
||
<tr>
|
||
<td>${index + 1}</td>
|
||
<td>${order.client}</td>
|
||
<td>${order.region}</td>
|
||
<td>${order.service}</td>
|
||
<td><span class="status-chip status-new">${order.status}</span></td>
|
||
<td class="amount">${formatAmount(order.amount)}</td>
|
||
</tr>
|
||
`).join('');
|
||
}
|
||
|
||
function updateKPI(orders) {
|
||
const totalSum = orders.reduce((sum, o) => sum + o.amount, 0);
|
||
const avg = Math.round(totalSum / orders.length);
|
||
|
||
// Подсчёт по регионам
|
||
const regionCount = {};
|
||
orders.forEach(o => {
|
||
regionCount[o.region] = (regionCount[o.region] || 0) + 1;
|
||
});
|
||
const topRegion = Object.entries(regionCount).sort((a, b) => b[1] - a[1])[0]?.[0] || '—';
|
||
|
||
document.getElementById('totalOrders').textContent = orders.length;
|
||
document.getElementById('top10Sum').textContent = formatAmount(totalSum) + ' ₸';
|
||
document.getElementById('avgCheck').textContent = formatAmount(avg) + ' ₸';
|
||
document.getElementById('topRegion').textContent = topRegion;
|
||
}
|
||
|
||
function updateLastUpdate() {
|
||
const now = new Date();
|
||
lastUpdateTime = now;
|
||
const dateStr = now.toLocaleDateString('ru-RU', { day: 'numeric', month: 'long', year: 'numeric' });
|
||
const timeStr = now.toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit' });
|
||
document.getElementById('lastUpdate').textContent = dateStr + ' в ' + timeStr;
|
||
}
|
||
|
||
async function fetchFromAlem() {
|
||
// Попытка получить данные от Alem
|
||
try {
|
||
const response = await fetch('/api/alem-orders');
|
||
if (!response.ok) throw new Error('Alem API недоступен');
|
||
const data = await response.json();
|
||
return data.orders || [];
|
||
} catch (e) {
|
||
console.log('Alem недоступен, используем мок-данные');
|
||
return mockOrders;
|
||
}
|
||
}
|
||
|
||
async function refreshData() {
|
||
const tbody = document.getElementById('ordersTable');
|
||
tbody.innerHTML = '<tr><td colspan="6" class="loading">Обновление данных…</td></tr>';
|
||
|
||
ordersData = await fetchFromAlem();
|
||
renderTable(ordersData);
|
||
updateKPI(ordersData);
|
||
updateLastUpdate();
|
||
}
|
||
|
||
// Инициализация при загрузке
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
refreshData();
|
||
});
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|