// frontend/js/app.js
import * as api from './api.js';
const PLATFORMS = [
{ id: 'ozon', name: 'Ozon' },
{ id: 'wb', name: 'Wildberries' },
{ id: 'ym', name: 'Yandex Market' },
{ id: 'mm', name: 'Magnit Market' },
{ id: 'mega', name: 'MegaMarket' },
{ id: 'ae', name: 'AliExpress' },
{ id: 'avito', name: 'Avito' },
{ id: 'lamoda', name: 'Lamoda' },
{ id: 'lemana', name: 'Lemana PRO' },
{ id: 'flowwow', name: 'Flowwow' }
];
let currentUser = null;
let socket = null;
function showToast(message, type = 'info') {
const container = document.getElementById('toast-container');
const toast = document.createElement('div');
toast.className = `toast ${type}`;
let icon = 'info';
if(type === 'success') icon = 'check-circle';
if(type === 'error') icon = 'alert-circle';
toast.innerHTML = `
${message}
`;
container.appendChild(toast);
if (window.lucide) lucide.createIcons();
setTimeout(() => {
toast.style.opacity = '0';
toast.style.transform = 'translateX(100%)';
setTimeout(() => toast.remove(), 300);
}, 4000);
}
function initSocket() {
// Подключаемся напрямую к бэкенду на порту 5000
try { socket = io(`http://${window.location.hostname}:5000`); } catch(e) { console.warn('Socket.IO:', e.message); return; }
socket.on('connect', () => {
console.log('Connected to WebSocket server');
});
socket.on('case_created', (data) => {
showToast(data.message, 'success');
// Refresh dashboard if active
if(!document.getElementById('dashboard-section').classList.contains('hidden')) {
loadDashboard();
}
});
socket.on('scanner_update', (data) => {
showToast(data.message, 'info');
});
}
document.addEventListener("DOMContentLoaded", async () => {
initAuth();
initReports();
try { initSocket(); } catch(e) { console.warn('Socket.IO не подключен:', e.message); }
// Check if logged in
const token = localStorage.getItem('token');
if (token) {
try {
currentUser = await api.getMe();
const planBadge = currentUser.plan === 'pro' ? '[PRO]' : '[FREE]';
document.getElementById('username-display').innerHTML = `Привет, ${currentUser.username} ${planBadge}`;
showApp();
if (window.lucide) lucide.createIcons();
} catch (e) {
showAuth();
}
} else {
showAuth();
}
});
let isLoginMode = true;
function initAuth() {
const authForm = document.getElementById('auth-form');
const toggleBtn = document.getElementById('auth-toggle');
const title = document.getElementById('auth-title');
const submitBtn = document.getElementById('auth-submit-btn');
const logoutBtn = document.getElementById('logout-btn');
toggleBtn.addEventListener('click', (e) => {
e.preventDefault();
isLoginMode = !isLoginMode;
title.textContent = isLoginMode ? 'Вход в систему' : 'Регистрация';
toggleBtn.textContent = isLoginMode ? 'Нет аккаунта? Зарегистрироваться' : 'Уже есть аккаунт? Войти';
submitBtn.textContent = isLoginMode ? 'Войти' : 'Зарегистрироваться';
});
authForm.addEventListener('submit', async (e) => {
e.preventDefault();
const username = document.getElementById('auth-username').value;
const password = document.getElementById('auth-password').value;
try {
if (isLoginMode) {
const res = await api.login(username, password);
localStorage.setItem('token', res.token);
// Сразу показываем приложение без перезагрузки
currentUser = await api.getMe();
const planBadge = currentUser.plan === 'pro' ? '[PRO]' : '[FREE]';
document.getElementById('username-display').innerHTML = `Привет, ${currentUser.username} ${planBadge}`;
showApp();
if (window.lucide) lucide.createIcons();
} else {
await api.register(username, password);
isLoginMode = true;
title.textContent = 'Вход в систему';
toggleBtn.textContent = 'Нет аккаунта? Зарегистрироваться';
submitBtn.textContent = 'Войти';
showToast('Регистрация успешна! Войдите в систему.', 'success');
}
} catch (err) {
alert(err.message);
}
});
logoutBtn.addEventListener('click', () => {
localStorage.removeItem('token');
window.location.reload();
});
}
function showAuth() {
document.getElementById('auth-section').classList.remove('hidden');
document.getElementById('main-app').classList.add('hidden');
document.getElementById('user-info').classList.add('hidden');
}
function showApp() {
document.getElementById('auth-section').classList.add('hidden');
document.getElementById('main-app').classList.remove('hidden');
document.getElementById('user-info').classList.remove('hidden');
try { initTabs(); } catch(e) { console.error('initTabs:', e); }
try { initCases(); } catch(e) { console.error('initCases:', e); }
try { loadIntegrations(); } catch(e) { console.error('loadIntegrations:', e); }
try { initBilling(); } catch(e) { console.error('initBilling:', e); }
try { initDashboard(); } catch(e) { console.error('initDashboard:', e); }
}
function initDashboard() {
loadDashboard();
}
let casesChart = null;
async function loadDashboard() {
try {
const data = await api.getDashboardStats();
// Update Counters
document.getElementById('stat-cases').innerText = data.total_cases;
document.getElementById('stat-integrations').innerText = data.total_integrations;
document.getElementById('stat-sales').innerText = `${(data.total_sales || 0).toLocaleString()} ₽`;
// Update Stores Table (Multi-account)
const storesTbody = document.querySelector('#stores-table tbody');
if (storesTbody) {
storesTbody.innerHTML = '';
(data.stores || []).forEach(s => {
const tr = document.createElement('tr');
const oosClass = s.days_left < 7 ? 'color: var(--color-error); font-weight: bold;' : '';
tr.innerHTML = `
${s.name} |
${s.platform} |
● Online |
${s.stock} |
${s.days_left} дн. |
${(s.daily_sales || 0).toLocaleString()} ₽ |
`;
storesTbody.appendChild(tr);
});
}
// Render Chart
const ctx = document.getElementById('casesChart').getContext('2d');
if (casesChart) casesChart.destroy();
casesChart = new Chart(ctx, {
type: 'line',
data: {
labels: data.trend.map(t => t.date),
datasets: [{
label: 'Новые кейсы',
data: data.trend.map(t => t.count),
borderColor: '#3b82f6',
backgroundColor: 'rgba(59, 130, 246, 0.1)',
tension: 0.4,
fill: true,
borderWidth: 3,
pointRadius: 4,
pointBackgroundColor: '#3b82f6'
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { display: false } },
scales: {
y: { beginAtZero: true, grid: { color: 'rgba(255,255,255,0.05)' }, border: { display: false } },
x: { grid: { display: false }, border: { display: false } }
}
}
});
// Update Recent Activity Table
const tbody = document.querySelector('#recent-activity-table tbody');
if (tbody) {
tbody.innerHTML = '';
data.recent_activity.forEach(a => {
const tr = document.createElement('tr');
const pName = PLATFORMS.find(p => p.id === a.platform)?.name || a.platform;
const statusClass = a.status === 'new' ? 'status-new' : (a.status === 'closed' ? 'status-closed' : '');
tr.innerHTML = `
#${a.id} |
${pName} |
${a.article} |
${a.status} |
${new Date(a.date).toLocaleDateString()} |
`;
tbody.appendChild(tr);
});
}
if (window.lucide) lucide.createIcons();
} catch (e) {
console.error('Dashboard Load Error:', e);
}
}
function initBilling() {
document.getElementById('current-plan-display').textContent = currentUser.plan.toUpperCase();
document.getElementById('upgrade-btn').addEventListener('click', async () => {
if(currentUser.plan === 'pro') {
alert('У вас уже тариф PRO!');
return;
}
if(confirm('Перейти на PRO тариф (симуляция оплаты)?')) {
try {
await api.upgradePlan();
alert('Оплата прошла успешно! Вы теперь PRO.');
window.location.reload();
} catch (err) {
alert(err.message);
}
}
});
}
function initTabs() {
const btns = document.querySelectorAll('.tab-btn');
const sections = [
'dashboard-section', 'cases-section', 'integrations-section', 'billing-section',
'logs-section', 'reviews-section', 'seo-section', 'content-section',
'finance-section', 'generator-section', 'analytics-section', 'repricer-section'
];
btns.forEach(btn => {
btn.addEventListener('click', () => {
btns.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
const target = btn.dataset.target;
sections.forEach(s => {
const el = document.getElementById(s);
if (el) el.classList.add('hidden');
});
document.getElementById(target).classList.remove('hidden');
document.getElementById('case-form-section').classList.add('hidden');
document.getElementById('case-detail-section').classList.add('hidden');
if(target === 'dashboard-section') loadDashboard();
if(target === 'cases-section') loadCases();
if(target === 'integrations-section') loadIntegrations();
if(target === 'logs-section') loadLogs();
if(target === 'reviews-section') { /* No initial load needed */ }
if(target === 'seo-section') loadSEO();
if(target === 'content-section') loadReferences();
if(target === 'finance-section') { /* No initial load */ }
if(target === 'generator-section') { /* No initial load */ }
if(target === 'analytics-section') { /* No initial load */ }
if(target === 'repricer-section') loadRepricerRules();
});
});
}
async function loadRepricerRules() {
const table = document.getElementById('repricer-table').querySelector('tbody');
try {
const token = localStorage.getItem('token');
const res = await fetch('/api/repricer/rules', {
headers: { 'Authorization': `Bearer ${token}` }
});
const rules = await res.json();
table.innerHTML = '';
rules.forEach(r => {
const tr = document.createElement('tr');
tr.innerHTML = `
${r.product_name} ${r.sku} |
${r.min_price} - ${r.max_price} ₽ |
${r.current_price ? r.current_price + ' ₽' : '—'} |
${r.last_run ? new Date(r.last_run).toLocaleString() : 'Никогда'} |
${r.is_active ? 'Активно' : 'Пауза'} |
`;
table.appendChild(tr);
});
} catch (err) {
console.error('Error loading repricer rules:', err);
}
}
document.getElementById('repricer-form').addEventListener('submit', async (e) => {
e.preventDefault();
const fd = new FormData(e.target);
const data = Object.fromEntries(fd.entries());
data.is_active = true;
try {
const token = localStorage.getItem('token');
await fetch('/api/repricer/rules', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(data)
});
showToast('Правило сохранено', 'success');
loadRepricerRules();
e.target.reset();
} catch (err) {
alert('Ошибка сохранения');
}
});
document.getElementById('run-repricer-btn').addEventListener('click', async () => {
const btn = document.getElementById('run-repricer-btn');
btn.disabled = true;
btn.innerHTML = ' Обновляю...';
if (window.lucide) lucide.createIcons();
try {
const token = localStorage.getItem('token');
const res = await fetch('/api/repricer/run', {
method: 'POST',
headers: { 'Authorization': `Bearer ${token}` }
});
const data = await res.json();
showToast(`Обновлено ${data.updates.length} цен`, 'success');
loadRepricerRules();
} catch (err) {
alert('Ошибка при запуске репрайзера');
} finally {
btn.disabled = false;
btn.innerHTML = ' Обновить цены сейчас';
if (window.lucide) lucide.createIcons();
}
});
document.getElementById('ai-analyze-comp-btn').addEventListener('click', async () => {
const myDesc = document.getElementById('my-desc-input').value;
const compDesc = document.getElementById('comp-desc-input').value;
if (!compDesc) {
alert('Введите описание конкурента');
return;
}
const btn = document.getElementById('ai-analyze-comp-btn');
const originalHTML = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = ' Анализирую рынок...';
if (window.lucide) lucide.createIcons();
try {
const token = localStorage.getItem('token');
const res = await fetch('/api/ai/analyze-competitor', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({ my_desc: myDesc, comp_desc: compDesc })
});
const data = await res.json();
if (data.error) throw new Error(data.error);
document.getElementById('insight-result').classList.remove('hidden');
document.getElementById('insight-win-phrase').innerText = data.win_phrase;
document.getElementById('insight-strategy').innerText = data.strategy;
const wList = document.getElementById('insight-weakness');
wList.innerHTML = '';
data.competitor_weakness.forEach(w => {
const li = document.createElement('li');
li.innerText = w;
wList.appendChild(li);
});
const aList = document.getElementById('insight-advantages');
aList.innerHTML = '';
data.my_advantages.forEach(a => {
const li = document.createElement('li');
li.innerText = a;
aList.appendChild(li);
});
showToast('Анализ Deep Insight завершен!', 'success');
} catch (err) {
alert(err.message);
} finally {
btn.disabled = false;
btn.innerHTML = originalHTML;
if (window.lucide) lucide.createIcons();
}
});
document.getElementById('ai-gen-card-btn').addEventListener('click', async () => {
const title = document.getElementById('gen-title-input').value;
if (!title) {
alert('Введите название товара');
return;
}
const btn = document.getElementById('ai-gen-card-btn');
const originalText = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = ' Магия ИИ в процессе...';
if (window.lucide) lucide.createIcons();
try {
const token = localStorage.getItem('token');
const res = await fetch('/api/ai/generate-card', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({ title })
});
const data = await res.json();
if (data.error) throw new Error(data.error);
document.getElementById('gen-result-container').classList.remove('hidden');
document.getElementById('gen-description').innerText = data.description;
// Tags
const tagsDiv = document.getElementById('gen-tags');
tagsDiv.innerHTML = '';
data.seo_tags.forEach(tag => {
const span = document.createElement('span');
span.className = 'status-pill';
span.style.background = 'rgba(255,255,255,0.1)';
span.innerText = `#${tag}`;
tagsDiv.appendChild(span);
});
// Features
const featuresTbody = document.querySelector('#gen-features-table tbody');
featuresTbody.innerHTML = '';
for (const [key, val] of Object.entries(data.features)) {
const tr = document.createElement('tr');
tr.innerHTML = `${key} | ${val} | `;
featuresTbody.appendChild(tr);
}
// Image Prompts
const promptsDiv = document.getElementById('gen-image-prompts');
promptsDiv.innerHTML = '';
data.image_prompts.forEach((p, i) => {
const pBox = document.createElement('div');
pBox.style.background = 'rgba(0,0,0,0.2)';
pBox.style.padding = '10px';
pBox.style.borderRadius = '8px';
pBox.style.marginBottom = '10px';
pBox.style.fontSize = '0.8rem';
pBox.innerHTML = `Вариант ${i+1}:
${p}`;
promptsDiv.appendChild(pBox);
});
showToast('Карточка успешно сгенерирована!', 'success');
} catch (err) {
alert(err.message);
} finally {
btn.disabled = false;
btn.innerHTML = originalText;
if (window.lucide) lucide.createIcons();
}
});
document.getElementById('pnl-form').addEventListener('submit', async (e) => {
e.preventDefault();
const fd = new FormData(e.target);
const data = Object.fromEntries(fd.entries());
try {
const token = localStorage.getItem('token');
const res = await fetch('/api/finance/calculate-pnl', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(data)
});
const result = await res.json();
document.getElementById('pnl-results').classList.remove('hidden');
document.getElementById('pnl-net-profit').innerText = `${result.net_profit.toLocaleString()} ₽`;
document.getElementById('pnl-margin').innerText = `${result.margin} %`;
document.getElementById('pnl-roi').innerText = `${result.roi} %`;
document.getElementById('pnl-total-costs').innerText = `${result.total_costs.toLocaleString()} ₽`;
document.getElementById('pnl-tax-abs').innerText = `${result.tax_amount.toLocaleString()} ₽`;
document.getElementById('pnl-comm-abs').innerText = `${result.commission_amount.toLocaleString()} ₽`;
// Color coding
const netProfitEl = document.getElementById('pnl-net-profit');
if (result.net_profit > 0) {
netProfitEl.style.color = 'var(--color-success)';
} else {
netProfitEl.style.color = 'var(--color-error)';
}
showToast('Расчет завершен', 'success');
} catch (err) {
alert('Ошибка расчета');
}
});
async function loadReferences() {
const list = document.getElementById('reference-list');
list.innerHTML = 'Загрузка...';
try {
const token = localStorage.getItem('token');
const res = await fetch('/api/content/reference', {
headers: { 'Authorization': `Bearer ${token}` }
});
const refs = await res.json();
list.innerHTML = '';
refs.forEach(r => {
const div = document.createElement('div');
div.className = 'platform-card';
div.style.textAlign = 'center';
div.innerHTML = `
#${r.article}
Hash: ${r.hash.substring(0, 8)}...
`;
list.appendChild(div);
});
} catch (e) {
list.innerHTML = 'Ошибка';
}
}
window.detectClones = async (id) => {
const container = document.getElementById('clones-detected-container');
const list = document.getElementById('clones-list');
list.innerHTML = 'Ищу визуальные копии на маркетплейсах...';
container.classList.remove('hidden');
try {
const token = localStorage.getItem('token');
const res = await fetch(`/api/content/detect-clones/${id}`, {
headers: { 'Authorization': `Bearer ${token}` }
});
const clones = await res.json();
list.innerHTML = '';
clones.forEach(c => {
const div = document.createElement('div');
div.className = 'platform-card';
div.style.borderColor = 'var(--color-error)';
div.innerHTML = `
${c.platform.toUpperCase()}
Артикул: ${c.article}
Сходство: ${c.similarity}
`;
list.appendChild(div);
});
} catch (e) {
list.innerHTML = 'Ошибка при поиске';
}
}
window.reportClone = (platform, url, article) => {
// Switch to cases tab and pre-fill form
document.querySelector('.tab-btn[data-target="cases-section"]').click();
setTimeout(() => {
document.getElementById('case-platform').value = platform;
document.querySelector('input[name="violator_url"]').value = url;
document.querySelector('input[name="violator_article"]').value = article;
showToast('Данные нарушителя перенесены в форму жалобы', 'info');
}, 100);
}
document.getElementById('add-reference-form').addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const btn = e.target.querySelector('button');
btn.disabled = true;
btn.innerText = 'Загрузка...';
try {
const token = localStorage.getItem('token');
const res = await fetch('/api/content/reference', {
method: 'POST',
headers: { 'Authorization': `Bearer ${token}` },
body: formData
});
if (res.ok) {
showToast('Эталон загружен и хеширован', 'success');
e.target.reset();
loadReferences();
}
} catch (err) {
alert('Ошибка');
} finally {
btn.disabled = false;
btn.innerText = 'Сохранить эталон';
}
});
let seoChart = null;
async function loadSEO() {
const tbody = document.querySelector('#seo-table tbody');
tbody.innerHTML = '| Загрузка... |
';
try {
const token = localStorage.getItem('token');
const res = await fetch('/api/seo/keywords', {
headers: { 'Authorization': `Bearer ${token}` }
});
const keywords = await res.json();
tbody.innerHTML = '';
keywords.forEach(k => {
const tr = document.createElement('tr');
tr.innerHTML = `
${k.keyword} |
${k.article} |
#${k.last_position || '?'} |
|
`;
tbody.appendChild(tr);
});
} catch (e) {
tbody.innerHTML = '| Ошибка загрузки |
';
}
}
window.viewSEOHistory = async (id, keyword) => {
try {
const token = localStorage.getItem('token');
const res = await fetch(`/api/seo/history/${id}`, {
headers: { 'Authorization': `Bearer ${token}` }
});
const history = await res.json();
document.getElementById('seo-chart-container').classList.remove('hidden');
document.getElementById('seo-chart-title').innerText = `История позиции: ${keyword}`;
const ctx = document.getElementById('seoChart').getContext('2d');
if (seoChart) seoChart.destroy();
seoChart = new Chart(ctx, {
type: 'line',
data: {
labels: history.map(h => h.date),
datasets: [{
label: 'Место в поиске',
data: history.map(h => h.position),
borderColor: '#10b981',
backgroundColor: 'rgba(16, 185, 129, 0.1)',
tension: 0.3,
fill: true,
reverse: true // Because 1 is better than 100
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: { reverse: true, grid: { color: 'rgba(255,255,255,0.05)' } },
x: { grid: { display: false } }
}
}
});
} catch (e) {
alert('Ошибка загрузки истории');
}
}
document.getElementById('add-seo-form').addEventListener('submit', async (e) => {
e.preventDefault();
const fd = new FormData(e.target);
const data = Object.fromEntries(fd.entries());
try {
const token = localStorage.getItem('token');
const res = await fetch('/api/seo/keywords', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(data)
});
if (res.ok) {
e.target.reset();
loadSEO();
showToast('Ключевое слово добавлено', 'success');
}
} catch (err) {
alert('Ошибка при добавлении');
}
});
async function loadLogs() {
const tbody = document.querySelector('#logs-table tbody');
tbody.innerHTML = '| Загрузка... |
';
try {
const token = localStorage.getItem('token');
const res = await fetch('/api/dashboard/logs', {
headers: { 'Authorization': `Bearer ${token}` }
});
const logs = await res.json();
tbody.innerHTML = '';
logs.forEach(l => {
const tr = document.createElement('tr');
const details = l.details ? JSON.stringify(l.details) : '-';
tr.innerHTML = `
${l.action} |
${details} |
${new Date(l.date).toLocaleString()} |
`;
tbody.appendChild(tr);
});
} catch (e) {
tbody.innerHTML = '| Ошибка загрузки |
';
}
}
// ---- INTEGRATIONS ----
async function loadIntegrations() {
const grid = document.getElementById('platforms-grid');
grid.innerHTML = 'Загрузка...';
try {
const configured = await api.getConfiguredPlatforms();
grid.innerHTML = '';
PLATFORMS.forEach(p => {
const isConfigured = configured.includes(p.id);
const card = document.createElement('div');
card.className = `platform-card ${isConfigured ? 'active-platform' : ''}`;
card.innerHTML = `
${p.name}
${isConfigured ? '✅ Подключено' : '❌ Не настроено'}
`;
card.querySelector('form').addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const data = Object.fromEntries(formData);
try {
await api.saveCredentials(p.id, data);
alert(`Настройки для ${p.name} сохранены!`);
loadIntegrations();
} catch(err) {
alert(err.message);
}
});
grid.appendChild(card);
});
if (window.lucide) lucide.createIcons();
} catch (e) {
grid.innerHTML = 'Ошибка загрузки интеграций.';
console.error(e);
}
}
// ---- CASES ----
async function initCases() {
document.getElementById('new-case-btn').addEventListener('click', () => {
document.getElementById('cases-section').classList.add('hidden');
document.getElementById('case-form-section').classList.remove('hidden');
document.getElementById('case-form').reset();
});
document.getElementById('cancel-btn').addEventListener('click', () => {
document.getElementById('case-form-section').classList.add('hidden');
document.getElementById('cases-section').classList.remove('hidden');
});
document.getElementById('back-to-list').addEventListener('click', () => {
document.getElementById('case-detail-section').classList.add('hidden');
document.getElementById('cases-section').classList.remove('hidden');
loadCases();
});
document.getElementById('search-input').addEventListener('input', debounce(loadCases, 500));
document.getElementById('status-filter').addEventListener('change', loadCases);
document.getElementById('platform-filter').addEventListener('change', loadCases);
document.getElementById('case-form').addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const data = Object.fromEntries(formData);
try {
const res = await api.createCase(data);
const fileInput = document.getElementById('file-input');
if (fileInput.files.length > 0) {
await api.uploadFiles(res.id, fileInput.files);
}
alert('Кейс успешно создан!');
document.getElementById('case-form-section').classList.add('hidden');
document.getElementById('cases-section').classList.remove('hidden');
loadCases();
} catch (err) {
console.error(err);
alert('Ошибка при сохранении');
}
});
document.getElementById('export-csv-btn').addEventListener('click', () => {
window.location.href = api.getExportCsvUrl();
});
document.getElementById('ai-gen-btn').addEventListener('click', async () => {
const platform = document.getElementById('case-platform').value;
const v_art = document.querySelector('input[name="violator_article"]').value;
const my_art = document.querySelector('input[name="my_article"]').value;
if (!v_art) {
alert('Сначала введите артикул нарушителя');
return;
}
const btn = document.getElementById('ai-gen-btn');
const originalText = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = ' Генерирую...';
if (window.lucide) lucide.createIcons();
try {
const token = localStorage.getItem('token');
const res = await fetch('/api/ai/generate-complaint', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({ platform, violator_article: v_art, my_article: my_art })
});
const data = await res.json();
if (data.text) {
document.getElementById('case-comment').value = data.text;
showToast('Текст успешно сгенерирован ИИ', 'success');
} else {
alert(data.error || 'Ошибка генерации');
}
} catch (err) {
alert('Ошибка сети при вызове ИИ');
} finally {
btn.disabled = false;
btn.innerHTML = originalText;
if (window.lucide) lucide.createIcons();
}
});
// ---- REVIEWS AI ----
document.getElementById('ai-review-btn').addEventListener('click', async () => {
const text = document.getElementById('review-text-input').value;
const rating = document.getElementById('review-rating-input').value;
if (!text) {
alert('Введите текст отзыва');
return;
}
const btn = document.getElementById('ai-review-btn');
const originalHTML = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = ' Генерирую ответ...';
if (window.lucide) lucide.createIcons();
try {
const token = localStorage.getItem('token');
const res = await fetch('/api/ai/generate-reply', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({ review_text: text, rating: parseInt(rating) })
});
const data = await res.json();
if (data.text) {
document.getElementById('ai-reply-output').value = data.text;
document.getElementById('ai-reply-container').classList.remove('hidden');
showToast('Ответ сгенерирован!', 'success');
} else {
alert(data.error || 'Ошибка');
}
} catch (err) {
alert('Ошибка сети');
} finally {
btn.disabled = false;
btn.innerHTML = originalHTML;
if (window.lucide) lucide.createIcons();
}
});
window.copyReply = () => {
const output = document.getElementById('ai-reply-output');
output.select();
document.execCommand('copy');
showToast('Скопировано в буфер!', 'info');
};
loadCases();
}
let currentViewCaseId = null;
async function initReports() {
document.getElementById('download-pdf-btn').addEventListener('click', () => {
if (currentViewCaseId) {
window.location.href = api.getCaseReportPdfUrl(currentViewCaseId);
}
});
}
// Дублирующий loadDashboard удалён — используется функция из строки ~149
window.viewCase = async function(id) {
currentViewCaseId = id;
document.getElementById('cases-section').classList.add('hidden');
const detailSection = document.getElementById('case-detail-section');
detailSection.classList.remove('hidden');
const caseObj = await api.getCase(id);
const files = await api.listFiles(id);
let html = `
Площадка: ${PLATFORMS.find(p => p.id === caseObj.platform)?.name || caseObj.platform}
Артикул нарушителя: ${caseObj.violator_article}
Ссылка: ${caseObj.violator_url}
Ваш артикул: ${caseObj.my_article || '-'}
Комментарий: ${caseObj.comment || '-'}
Статус: ${caseObj.status}
`;
if (files && files.length > 0) {
html += `Файлы доказательств:
`;
files.forEach(f => {
const url = `/api/uploads/${f.path.split('/').pop()}`;
html += `

`;
});
html += `
`;
}
document.getElementById('case-detail').innerHTML = html;
};
window.deleteCase = async function(id) {
if(confirm('Удалить этот кейс?')) {
await api.deleteCase(id);
loadCases();
}
};
function debounce(func, wait) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}