v1.8.0: YandexGPT-only NLP, unknown questions system, callback dedup, consent flow fixes, DELETE cascade, UTC+3, auto-detect sidebar menu, tel:/mailto: links

This commit is contained in:
2026-06-01 18:07:13 +03:00
parent aa1269e013
commit cc87bcf72c
20 changed files with 1453 additions and 301 deletions
@@ -1,7 +1,7 @@
{% extends "page.html" %}
{% block page_content %}
<style>
.tickets-table { width:100%; border-collapse:collapse; }
.tickets-table { width:100%; border-collapse:collapse; overflow-x:auto; display:block; }
.tickets-table th, .tickets-table td { padding:10px 14px; text-align:left; border-bottom:1px solid var(--border); font-size:13px; }
.tickets-table th { font-weight:600; color:var(--text-muted); background:var(--bg-card); }
.tickets-table td { vertical-align:middle; }
@@ -18,6 +18,8 @@
.ticket-detail-card .field .value { font-size:14px; }
.filter-bar { display:flex; gap:12px; margin-bottom:16px; align-items:center; flex-wrap:wrap; }
.filter-bar select, .filter-bar input { padding:8px 12px; border:1px solid var(--border); border-radius:6px; font-size:13px; }
.contact-info { font-size:12px; color:var(--text-muted); }
.contact-info strong { color:var(--text-primary); }
</style>
<div class="card">
@@ -42,6 +44,7 @@
<tr>
<th>ID</th>
<th>Заголовок</th>
<th>Контакт</th>
<th>Приоритет</th>
<th>Статус</th>
<th>Дата</th>
@@ -49,7 +52,7 @@
</tr>
</thead>
<tbody id="tickets-body">
<tr><td colspan="6" style="text-align:center;padding:30px;color:var(--text-muted);">Загрузка...</td></tr>
<tr><td colspan="7" style="text-align:center;padding:30px;color:var(--text-muted);">Загрузка...</td></tr>
</tbody>
</table>
</div>
@@ -57,6 +60,7 @@
<div class="ticket-detail-card" id="ticket-detail">
<h3 id="ticket-detail-title">Заявка #</h3>
<div class="field"><label>Описание</label><div class="value" id="ticket-detail-desc"></div></div>
<div class="field"><label>Контактные данные</label><div class="value" id="ticket-detail-contact"></div></div>
<div class="field"><label>Приоритет</label><div class="value" id="ticket-detail-priority"></div></div>
<div class="field"><label>Статус</label>
<select id="ticket-detail-status" onchange="updateTicketStatus()">
@@ -75,18 +79,21 @@ var currentTicketId = null;
function loadTickets(){
var tbody = document.getElementById('tickets-body');
tbody.innerHTML = '<tr><td colspan="6" style="text-align:center;padding:30px;color:var(--text-muted);">Загрузка...</td></tr>';
tbody.innerHTML = '<tr><td colspan="7" style="text-align:center;padding:30px;color:var(--text-muted);">Загрузка...</td></tr>';
fetch('/api/bot/tickets').then(function(r){ return r.json() }).then(function(data){
allTickets = data;
document.getElementById('ticket-count').textContent = data.length + ' заявок';
renderTickets(data);
}).catch(function(err){
tbody.innerHTML = '<tr><td colspan="7" style="text-align:center;padding:30px;color:var(--danger);">Ошибка загрузки</td></tr>';
console.error('loadTickets error:', err);
});
}
function renderTickets(tickets){
var tbody = document.getElementById('tickets-body');
if(!tickets.length){
tbody.innerHTML = '<tr><td colspan="6" style="text-align:center;padding:30px;color:var(--text-muted);">Нет заявок</td></tr>';
tbody.innerHTML = '<tr><td colspan="7" style="text-align:center;padding:30px;color:var(--text-muted);">Нет заявок</td></tr>';
return;
}
var html = '';
@@ -95,9 +102,11 @@ function renderTickets(tickets){
if(t.status === 'В работе') statusClass = 'status-v-rabote';
else if(t.status === 'Закрыта') statusClass = 'status-zakryta';
var dateStr = t.created_at ? new Date(t.created_at).toLocaleString() : '';
var contact = t.contact_name || t.contact_phone || t.contact_email || '—';
html += '<tr>' +
'<td>' + t.id + '</td>' +
'<td><a href="javascript:void(0)" onclick="showDetail(' + t.id + ')">' + escapeHtml(t.title) + '</a></td>' +
'<td class="contact-info"><strong>' + escapeHtml(t.contact_name || '—') + '</strong><br>' + escapeHtml(t.contact_phone || '') + (t.contact_phone && t.contact_email ? '<br>' : '') + escapeHtml(t.contact_email || '') + '</td>' +
'<td>' + t.priority + '</td>' +
'<td><span class="status-badge ' + statusClass + '">' + t.status + '</span></td>' +
'<td style="font-size:12px;color:var(--text-muted);">' + dateStr + '</td>' +
@@ -131,6 +140,12 @@ function showDetail(id){
if(!ticket) return;
document.getElementById('ticket-detail-title').textContent = 'Заявка #' + ticket.id;
document.getElementById('ticket-detail-desc').textContent = ticket.description || '—';
var contactHtml = '';
if(ticket.contact_name) contactHtml += '<strong>' + escapeHtml(ticket.contact_name) + '</strong><br>';
if(ticket.contact_phone) contactHtml += 'Тел: ' + escapeHtml(ticket.contact_phone) + '<br>';
if(ticket.contact_email) contactHtml += 'Email: ' + escapeHtml(ticket.contact_email);
if(!contactHtml) contactHtml = '—';
document.getElementById('ticket-detail-contact').innerHTML = contactHtml;
document.getElementById('ticket-detail-priority').textContent = ticket.priority;
document.getElementById('ticket-detail-status').value = ticket.status;
document.getElementById('ticket-detail-date').textContent = ticket.created_at ? new Date(ticket.created_at).toLocaleString() : '—';