Files
site_aegisone/py_service/app/templates/pages/bot_tickets.html
T

196 lines
9.3 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.
{% extends "page.html" %}
{% block page_content %}
<style>
.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; }
.status-badge { display:inline-block; padding:3px 10px; border-radius:12px; font-size:12px; font-weight:600; }
.status-nova { background:var(--info-bg, #cce5ff); color:var(--info, #004085); }
.status-v-rabote { background:var(--warning-bg, #fff3cd); color:var(--warning, #856404); }
.status-zakryta { background:var(--success-bg, #d4edda); color:var(--success, #155724); }
.status-select { padding:4px 8px; border-radius:6px; border:1px solid var(--border); font-size:12px; }
.ticket-detail-card { background:var(--bg-card); border:1px solid var(--border); border-radius:12px; padding:20px; margin-top:16px; display:none; }
.ticket-detail-card.open { display:block; }
.ticket-detail-card h3 { margin-bottom:12px; }
.ticket-detail-card .field { margin-bottom:8px; }
.ticket-detail-card .field label { font-weight:600; font-size:12px; color:var(--text-muted); display:block; }
.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">
<div class="card-header" style="display:flex;justify-content:space-between;align-items:center;">
<h2>Заявки от чат-бота</h2>
<span id="ticket-count" style="font-size:13px;color:var(--text-muted);">0 заявок</span>
</div>
<div class="filter-bar">
<select id="status-filter" onchange="filterTickets()">
<option value="">Все статусы</option>
<option value="Новая">Новая</option>
<option value="В работе">В работе</option>
<option value="Закрыта">Закрыта</option>
</select>
<input type="text" id="ticket-search" placeholder="Поиск..." oninput="filterTickets()">
<button class="btn btn-sm" onclick="loadTickets()">Обновить</button>
</div>
<table class="tickets-table">
<thead>
<tr>
<th>ID</th>
<th>Заголовок</th>
<th>Контакт</th>
<th>Приоритет</th>
<th>Статус</th>
<th>Дата</th>
<th>Действия</th>
</tr>
</thead>
<tbody id="tickets-body">
<tr><td colspan="7" style="text-align:center;padding:30px;color:var(--text-muted);">Загрузка...</td></tr>
</tbody>
</table>
</div>
<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()">
<option value="Новая">Новая</option>
<option value="В работе">В работе</option>
<option value="Закрыта">Закрыта</option>
</select>
</div>
<div class="field"><label>Дата</label><div class="value" id="ticket-detail-date"></div></div>
<button class="btn" onclick="closeDetail()">Закрыть</button>
</div>
<script>
var allTickets = [];
var currentTicketId = null;
function loadTickets(){
var tbody = document.getElementById('tickets-body');
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="7" style="text-align:center;padding:30px;color:var(--text-muted);">Нет заявок</td></tr>';
return;
}
var html = '';
tickets.forEach(function(t){
var statusClass = 'status-nova';
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>' +
'<td>' +
' <select class="status-select" onchange="changeStatus(' + t.id + ', this.value)">' +
' <option value="">Сменить статус</option>' +
' <option value="Новая">Новая</option>' +
' <option value="В работе">В работе</option>' +
' <option value="Закрыта">Закрыта</option>' +
' </select>' +
'</td>' +
'</tr>';
});
tbody.innerHTML = html;
if(typeof sortTables === 'function') sortTables();
}
function filterTickets(){
var statusFilter = document.getElementById('status-filter').value;
var search = document.getElementById('ticket-search').value.toLowerCase();
var filtered = allTickets.filter(function(t){
if(statusFilter && t.status !== statusFilter) return false;
if(search && !t.title.toLowerCase().includes(search) && !(t.description || '').toLowerCase().includes(search)) return false;
return true;
});
renderTickets(filtered);
}
function showDetail(id){
currentTicketId = id;
var ticket = allTickets.find(function(t){ return t.id === 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() : '—';
document.getElementById('ticket-detail').classList.add('open');
}
function closeDetail(){
document.getElementById('ticket-detail').classList.remove('open');
currentTicketId = null;
}
function changeStatus(id, status){
if(!status) return;
updateTicketStatusApi(id, status);
}
function updateTicketStatus(){
if(currentTicketId){
var status = document.getElementById('ticket-detail-status').value;
updateTicketStatusApi(currentTicketId, status);
}
}
function updateTicketStatusApi(id, status){
fetch('/api/bot/tickets/' + id, {
method: 'PATCH',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({status: status})
}).then(function(r){ return r.json() }).then(function(d){
if(d.ok){
loadTickets();
if(currentTicketId === id) showDetail(id);
} else {
showNotification('Ошибка: ' + (d.error || 'неизвестная'), 'error');
}
});
}
function escapeHtml(s){
if(!s) return '';
return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
}
loadTickets();
</script>
{% endblock %}