crm/index.html

190 lines
8.8 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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>