proekt-po-crm/index.html

184 lines
9.9 KiB
HTML
Raw Permalink 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">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>CRM Dashboard — ДРБ</title>
<style>
* { margin: 0; box-sizing: border-box; }
body { font-family: -apple-system, "Segoe UI", Roboto, sans-serif; background: #0f1218; color: #f4f4f5; padding: 20px; }
.container { max-width: 1200px; margin: 0 auto; }
h1 { font-size: 28px; margin-bottom: 10px; }
h2 { font-size: 20px; margin: 30px 0 15px; color: #00b8cc; }
.presets { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 20px; }
.preset-btn { background: #1a1d26; border: 1px solid #333; color: #f4f4f5; padding: 12px 20px; border-radius: 8px; cursor: pointer; font-size: 14px; transition: all 0.2s; }
.preset-btn:hover { border-color: #00b8cc; background: #1f2430; }
.preset-btn.primary { background: #00b8cc; border-color: #00b8cc; color: #0f1218; font-weight: 600; }
.preset-btn.primary:hover { background: #00d4ee; }
.custom-query { display: flex; gap: 10px; margin-bottom: 30px; }
.custom-query input { flex: 1; padding: 12px 16px; border: 1px solid #333; border-radius: 8px; background: #1a1d26; color: #f4f4f5; font-size: 14px; }
.custom-query input:focus { outline: none; border-color: #00b8cc; }
.custom-query button { padding: 12px 24px; background: #00b8cc; border: none; border-radius: 8px; color: #0f1218; font-weight: 600; cursor: pointer; }
.status-bar { background: #1a1d26; padding: 15px; border-radius: 8px; margin-bottom: 20px; display: flex; justify-content: space-between; align-items: center; }
.status-item { display: flex; align-items: center; gap: 8px; }
.status-dot { width: 10px; height: 10px; border-radius: 50%; }
.status-dot.pending { background: #f59e0b; }
.status-dot.completed { background: #10b981; }
.status-dot.error { background: #ef4444; }
.requests { display: flex; flex-direction: column; gap: 15px; }
.request-card { background: #1a1d26; border: 1px solid #333; border-radius: 10px; padding: 16px; }
.request-card.pending { border-left: 4px solid #f59e0b; }
.request-card.completed { border-left: 4px solid #10b981; }
.request-card.error { border-left: 4px solid #ef4444; }
.request-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
.request-query { font-weight: 500; color: #e4e4e7; }
.request-meta { font-size: 12px; color: #71717a; }
.request-response { background: #0f1218; padding: 12px; border-radius: 6px; font-family: monospace; font-size: 13px; white-space: pre-wrap; color: #a1a1aa; max-height: 400px; overflow-y: auto; }
.empty-state { text-align: center; padding: 60px 20px; color: #71717a; }
.clear-btn { background: transparent; border: 1px solid #ef4444; color: #ef4444; padding: 8px 16px; border-radius: 6px; cursor: pointer; font-size: 13px; }
.clear-btn:hover { background: #ef4444; color: #fff; }
.loading { animation: pulse 1.5s infinite; }
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
.badge { display: inline-block; padding: 4px 10px; border-radius: 20px; font-size: 11px; font-weight: 600; text-transform: uppercase; }
.badge-pending { background: #f59e0b; color: #0f1218; }
.badge-completed { background: #10b981; color: #0f1218; }
.badge-error { background: #ef4444; color: #fff; }
</style>
</head>
<body>
<div class="container">
<h1>📊 CRM Dashboard — ДРБ</h1>
<p style="color: #71717a; margin-bottom: 20px;">Панель запросов к Alem-агенту по данным CRM</p>
<h2>Готовые запросы</h2>
<div class="presets">
<button class="preset-btn primary" onclick="sendQuery('Продажи за вчерашний день по ДРБ: количество, сумма, топ продуктов', 'Продажи ДРБ')">📈 Продажи ДРБ за вчера</button>
<button class="preset-btn" onclick="sendQuery('Воронка продаж по филиалам ДРБ за неделю: количество заказов по статусам', 'Воронка')">🔄 Воронка продаж</button>
<button class="preset-btn" onclick="sendQuery('Топ 10 продуктов по количеству продаж за последние 7 дней', 'Топ продуктов')">🏆 Топ продуктов</button>
<button class="preset-btn" onclick="sendQuery('Количество отказов по причинам за вчера', 'Отказы')">❌ Отказы</button>
<button class="preset-btn" onclick="sendQuery('Сравнение продаж этой недели с прошлой неделей по ДРБ', 'Сравнение')">📊 Сравнение недель</button>
<button class="preset-btn" onclick="sendQuery('Статусы заказов по каналам продаж за вчера', 'Каналы')">📱 Каналы продаж</button>
</div>
<h2>Свой запрос</h2>
<div class="custom-query">
<input type="text" id="customQuery" placeholder="Напишите свой запрос к агенту..." onkeypress="if(event.key==='Enter') sendCustom()">
<button onclick="sendCustom()">Отправить</button>
</div>
<div class="status-bar">
<div class="status-item">
<span class="status-dot" id="statusDot"></span>
<span id="statusText">Ожидание запросов...</span>
</div>
<button class="clear-btn" onclick="clearData()">Очистить историю</button>
</div>
<h2>История запросов</h2>
<div id="requestsList" class="requests">
<div class="empty-state">Нет запросов. Выберите готовый preset или напишите свой запрос.</div>
</div>
</div>
<script>
const PRESET_LABELS = {
'Продажи ДРБ': '📈 Продажи ДРБ',
'Воронка': '🔄 Воронка',
'Топ продуктов': '🏆 Топ продуктов',
'Отказы': '❌ Отказы',
'Сравнение': '📊 Сравнение',
'Каналы': '📱 Каналы'
};
async function sendQuery(query, preset) {
const resp = await fetch("/api/request", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query, preset })
});
const data = await resp.json();
if (data.ok) {
document.getElementById("customQuery").value = "";
loadRequests();
}
}
function sendCustom() {
const query = document.getElementById("customQuery").value.trim();
if (query) sendQuery(query, null);
}
async function loadRequests() {
const resp = await fetch("/api/requests");
const data = await resp.json();
render(data.requests, data.responses);
}
function render(requests, responses) {
const container = document.getElementById("requestsList");
if (!requests || requests.length === 0) {
container.innerHTML = '<div class="empty-state">Нет запросов. Выберите готовый preset или напишите свой запрос.</div>';
updateStatus("Ожидание запросов...", null);
return;
}
const respMap = {};
(responses || []).forEach(r => { respMap[r.id] = r; });
const pending = requests.filter(r => r.status === "pending").length;
const statusText = pending > 0
? `Ожидает ответа: ${pending} запрос(ов)`
: "Все запросы обработаны";
const statusClass = pending > 0 ? "pending" : (requests.length > 0 ? "completed" : null);
updateStatus(statusText, statusClass);
container.innerHTML = requests.map(r => {
const resp = respMap[r.id];
const badgeClass = r.status === "pending" ? "badge-pending" : r.status === "error" ? "badge-error" : "badge-completed";
const badgeText = r.status === "pending" ? "В обработке" : r.status === "error" ? "Ошибка" : "Готово";
const presetLabel = r.preset ? PRESET_LABELS[r.preset] || r.preset : null;
return `
<div class="request-card ${r.status}">
<div class="request-header">
<div>
${presetLabel ? '<span style="font-size:12px;color:#00b8cc;margin-right:8px;">'+presetLabel+'</span>' : ''}
<span class="request-query">${escapeHtml(r.query)}</span>
</div>
<span class="badge ${badgeClass}">${badgeText}</span>
</div>
<div class="request-meta">
${new Date(r.created).toLocaleString("ru-RU")}
${resp && resp.updated ? ' • Ответ: ' + new Date(resp.updated).toLocaleString("ru-RU") : ''}
</div>
${resp && resp.response ? '<div class="request-response">' + escapeHtml(resp.response) + '</div>' : ''}
${r.status === "pending" ? '<div class="request-response loading">⏳ Запрос отправлен агенту... Ожидайте ответа в чате.</div>' : ''}
</div>
`;
}).join("");
}
function updateStatus(text, statusClass) {
document.getElementById("statusText").textContent = text;
const dot = document.getElementById("statusDot");
dot.className = "status-dot " + (statusClass || "");
}
function escapeHtml(str) {
if (!str) return "";
return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}
async function clearData() {
if (!confirm("Очистить всю историю запросов?")) return;
await fetch("/api/clear", { method: "DELETE" });
loadRequests();
}
// Автообновление каждые 5 секунд
setInterval(loadRequests, 5000);
loadRequests();
</script>
</body>
</html>