216 lines
8.4 KiB
JavaScript
216 lines
8.4 KiB
JavaScript
// 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' }
|
||
];
|
||
|
||
document.addEventListener("DOMContentLoaded", () => {
|
||
initTabs();
|
||
initCases();
|
||
initIntegrations();
|
||
});
|
||
|
||
function initTabs() {
|
||
const btns = document.querySelectorAll('.tab-btn');
|
||
const sections = ['cases-section', 'integrations-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 => {
|
||
document.getElementById(s).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 === 'cases-section') loadCases();
|
||
if(target === 'integrations-section') loadIntegrations();
|
||
});
|
||
});
|
||
}
|
||
|
||
// ---- 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 = `
|
||
<h3>${p.name}</h3>
|
||
<p class="status">${isConfigured ? '✅ Подключено' : '❌ Не настроено'}</p>
|
||
<form class="platform-form" data-id="${p.id}">
|
||
<input type="text" name="api_key" placeholder="API Key / Token" />
|
||
<input type="text" name="client_id" placeholder="Client ID (если есть)" />
|
||
<input type="text" name="client_secret" placeholder="Client Secret (если есть)" />
|
||
<button type="submit" class="btn-primary">Сохранить</button>
|
||
</form>
|
||
`;
|
||
|
||
card.querySelector('form').addEventListener('submit', async (e) => {
|
||
e.preventDefault();
|
||
const formData = new FormData(e.target);
|
||
const data = Object.fromEntries(formData);
|
||
await api.saveCredentials(p.id, data);
|
||
alert(`Настройки для ${p.name} сохранены!`);
|
||
loadIntegrations();
|
||
});
|
||
|
||
grid.appendChild(card);
|
||
});
|
||
} 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('Ошибка при сохранении');
|
||
}
|
||
});
|
||
|
||
loadCases();
|
||
}
|
||
|
||
async function loadCases() {
|
||
const article = document.getElementById('search-input').value;
|
||
const status = document.getElementById('status-filter').value;
|
||
const platform = document.getElementById('platform-filter').value;
|
||
|
||
const tbody = document.querySelector('#cases-table tbody');
|
||
tbody.innerHTML = '<tr><td colspan="6">Загрузка...</td></tr>';
|
||
|
||
try {
|
||
const cases = await api.fetchCases({ article, status, platform });
|
||
tbody.innerHTML = '';
|
||
if (cases.length === 0) {
|
||
tbody.innerHTML = '<tr><td colspan="6">Нет жалоб</td></tr>';
|
||
return;
|
||
}
|
||
|
||
cases.forEach(c => {
|
||
const tr = document.createElement('tr');
|
||
const pName = PLATFORMS.find(p => p.id === c.platform)?.name || c.platform;
|
||
tr.innerHTML = `
|
||
<td>${c.id}</td>
|
||
<td><strong>${pName}</strong></td>
|
||
<td>${c.violator_article}</td>
|
||
<td>${c.status}</td>
|
||
<td>${new Date(c.created_at).toLocaleString()}</td>
|
||
<td>
|
||
<button class="btn-secondary" onclick="window.viewCase(${c.id})">Просмотр</button>
|
||
<button class="btn-secondary" onclick="window.deleteCase(${c.id})">Удалить</button>
|
||
</td>
|
||
`;
|
||
tbody.appendChild(tr);
|
||
});
|
||
} catch (err) {
|
||
tbody.innerHTML = '<tr><td colspan="6">Ошибка сети</td></tr>';
|
||
}
|
||
}
|
||
|
||
window.viewCase = async function(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 = `
|
||
<p><strong>Площадка:</strong> ${PLATFORMS.find(p => p.id === caseObj.platform)?.name || caseObj.platform}</p>
|
||
<p><strong>Артикул нарушителя:</strong> ${caseObj.violator_article}</p>
|
||
<p><strong>Ссылка:</strong> <a href="${caseObj.violator_url}" target="_blank">${caseObj.violator_url}</a></p>
|
||
<p><strong>Ваш артикул:</strong> ${caseObj.my_article || '-'}</p>
|
||
<p><strong>Комментарий:</strong> ${caseObj.comment || '-'}</p>
|
||
<p><strong>Статус:</strong> ${caseObj.status}</p>
|
||
`;
|
||
|
||
if (files && files.length > 0) {
|
||
html += `<h3>Файлы доказательств:</h3><div class="preview">`;
|
||
files.forEach(f => {
|
||
const url = `http://localhost:5000/api/uploads/${f.path.split('/').pop()}`;
|
||
html += `<a href="${url}" target="_blank"><img src="${url}" alt="Док" /></a>`;
|
||
});
|
||
html += `</div>`;
|
||
}
|
||
|
||
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);
|
||
};
|
||
}
|