"use strict";
(function () {
let cachedPayload = null;
function core() {
return (window.YAStatsCabinet && window.YAStatsCabinet.core) || {};
}
function escapeHtml(value) {
if (typeof core().escapeHtml === "function") return core().escapeHtml(value);
return String(value || "")
.replace(/&/g, "&")
.replace(//g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
function formatDateTime(value) {
if (!value) return "—";
try {
return new Date(value).toLocaleString("ru-RU", {
day: "2-digit",
month: "2-digit",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
});
} catch (error) {
return String(value);
}
}
function fetchJson(url, options) {
return fetch(url, Object.assign({ credentials: "same-origin", cache: "no-store" }, options || {}))
.then(async (response) => {
const data = await response.json().catch(() => ({}));
if (!response.ok) throw new Error(data.detail || "request_failed");
return data;
});
}
function getUpgradeHtml() {
return `
Этот раздел доступен на тарифах Pro и Business .
На Pro можно отслеживать до 3 сайтов на 3 месяца, на Business — до 10 сайтов на 2 месяца.
`;
}
function statusBadge(item) {
const code = String(item.last_status || "pending");
const map = {
pending: "border-white/10 bg-white/10 text-white",
ok: "border-green-500/20 bg-green-500/20 text-green-200",
changed: "border-yellow-500/20 bg-yellow-500/20 text-yellow-100",
error: "border-red-500/20 bg-red-500/20 text-red-100",
};
const label = {
pending: "Ждет первой проверки",
ok: "Без критичных изменений",
changed: "Найдены изменения",
error: "Ошибка проверки",
};
return `${escapeHtml(label[code] || label.pending)} `;
}
function changeLevelBadge(item) {
const code = String(item.last_change_level || "");
if (!code || code === "new") return "";
const map = {
high: "bg-red-500/20 text-red-100 border-red-500/20",
medium: "bg-yellow-500/20 text-yellow-100 border-yellow-500/20",
low: "bg-white/10 text-gray-200 border-white/10",
error: "bg-red-500/20 text-red-100 border-red-500/20",
};
const label = String(item.last_change_label || "");
if (!label) return "";
return `${escapeHtml(label)} `;
}
function renderItems(items) {
if (!items.length) {
return `Пока нет ни одного сайта. Добавьте конкурента, и кабинет начнет хранить историю проверок, изменений и следующий срок мониторинга.
`;
}
return items.map((item) => {
const tracking = item.tracking || {};
const changes = Array.isArray(item.last_changes) ? item.last_changes : [];
const tags = [];
if (tracking.prices) tags.push("цены");
if (tracking.promotions) tags.push("акции");
if (tracking.offers) tags.push("оффер");
if (tracking.contacts) tags.push("контакты");
return `
${escapeHtml(item.label || item.host || item.url)}
${statusBadge(item)}
${item.last_status === "changed" || item.last_status === "error" ? changeLevelBadge(item) : ""}
${escapeHtml(item.url)}
Проверить сейчас
Удалить
Последняя проверка
${escapeHtml(formatDateTime(item.last_checked_at))}
Следующая проверка
${escapeHtml(formatDateTime(item.next_check_at))}
Мониторинг до
${escapeHtml(formatDateTime(item.monitor_until))}
${tags.map((tag) => `${escapeHtml(tag)} `).join("")}
${Array.isArray(item.watch_terms) && item.watch_terms.length ? `
Что ищем точечно
${item.watch_terms.map((term) => `${escapeHtml(term)} `).join("")}
` : ""}
${changes.length ? `
Что изменилось
${changes.map((change) => `
${escapeHtml(change)}
`).join("")}
` : ""}
${item.last_error ? `
${escapeHtml(item.last_error)}
` : ""}
`;
}).join("");
}
function renderModal(payload) {
cachedPayload = payload;
const modal = document.getElementById("competitor-modal");
const body = document.getElementById("competitor-modal-body");
if (!modal || !body) return;
const items = Array.isArray(payload.items) ? payload.items : [];
const policy = payload.policy || {};
const ideas = Array.isArray(payload.ideas) ? payload.ideas : [];
const used = items.length;
const limit = Number(policy.max_sites || 0);
body.innerHTML = `
Добавьте сайты конкурентов, а сервис будет раз в неделю перепроверять страницу и замечать изменения в ценах, акциях, оффере и контактах. Все найденные сдвиги попадут в активность кабинета, а для связанного Telegram будут уходить уведомления.
Новый сайт
Сейчас занято ${used} из ${limit} мест на тарифе ${escapeHtml(policy.label || "")}.
Срок мониторинга
${escapeHtml(String(policy.term_days || 0))} дней
Добавить в мониторинг
Сайты под наблюдением
${renderItems(items)}
Как это работает
1. Вы добавляете сайт конкурента и отмечаете, что для вас критично: цены, акции, оффер или контакты.
2. Сервис раз в 7 дней снимает свежий срез страницы и сравнивает его с прошлым состоянием.
3. Если что-то меняется, кабинет пишет понятное предупреждение, чтобы вы успели скорректировать свою рекламу или предложение.
Что еще полезно отслеживать
${ideas.map((idea) => `
${escapeHtml(idea)}
`).join("")}
Лимиты по тарифу
Pro: до 3 сайтов, срок наблюдения для каждого сайта — 3 месяца.
Business: до 10 сайтов, срок наблюдения для каждого сайта — 2 месяца.
Если сайт связан с Telegram, изменения дополнительно уходят в уведомления.
`;
bindActions();
}
function setStatus(message) {
const box = document.getElementById("competitor-status");
if (!box) return;
if (!message) {
box.textContent = "";
box.classList.add("hidden");
return;
}
box.textContent = String(message);
box.classList.remove("hidden");
}
function readForm() {
return {
url: document.getElementById("competitor-url") ? document.getElementById("competitor-url").value : "",
label: document.getElementById("competitor-label") ? document.getElementById("competitor-label").value : "",
watch_terms: document.getElementById("competitor-watch-terms") ? document.getElementById("competitor-watch-terms").value : "",
notes: document.getElementById("competitor-notes") ? document.getElementById("competitor-notes").value : "",
tracking: {
prices: !!(document.getElementById("competitor-track-prices") && document.getElementById("competitor-track-prices").checked),
promotions: !!(document.getElementById("competitor-track-promotions") && document.getElementById("competitor-track-promotions").checked),
offers: !!(document.getElementById("competitor-track-offers") && document.getElementById("competitor-track-offers").checked),
contacts: !!(document.getElementById("competitor-track-contacts") && document.getElementById("competitor-track-contacts").checked),
},
};
}
async function reloadModal() {
const payload = await fetchJson("/auth/site/competitors");
renderModal(payload);
}
function bindActions() {
const saveBtn = document.getElementById("competitor-save-btn");
if (saveBtn) {
saveBtn.addEventListener("click", async () => {
setStatus("");
try {
await fetchJson("/auth/site/competitors", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(readForm()),
});
await reloadModal();
} catch (error) {
setStatus(error && error.message ? error.message : "Не удалось добавить сайт.");
}
});
}
document.querySelectorAll(".competitor-delete-btn").forEach((button) => {
button.addEventListener("click", async () => {
setStatus("");
try {
await fetchJson("/auth/site/competitors/delete", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ url: button.dataset.url }),
});
await reloadModal();
} catch (error) {
setStatus(error && error.message ? error.message : "Не удалось удалить сайт.");
}
});
});
document.querySelectorAll(".competitor-check-btn").forEach((button) => {
button.addEventListener("click", async () => {
setStatus("");
try {
await fetchJson("/auth/site/competitors/check", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ url: button.dataset.url }),
});
await reloadModal();
} catch (error) {
setStatus(error && error.message ? error.message : "Не удалось проверить сайт.");
}
});
});
}
async function openCompetitorAnalysisModal() {
if (typeof window.openModal === "function") {
window.openModal("competitor-modal");
}
const body = document.getElementById("competitor-modal-body");
if (body) body.innerHTML = `Подгружаем список конкурентов...
`;
try {
const payload = await fetchJson("/auth/site/competitors");
renderModal(payload);
} catch (error) {
if (String(error && error.message || "").includes("только на тарифах")) {
if (body) body.innerHTML = getUpgradeHtml();
return;
}
if (body) {
body.innerHTML = `Раздел не открылся
${escapeHtml(error && error.message ? error.message : "Попробуйте позже.")}
`;
}
}
}
function bindButtons() {
["open-competitor-analysis-btn"].forEach((id) => {
const node = document.getElementById(id);
if (!node || node.dataset.competitorBound === "1") return;
node.dataset.competitorBound = "1";
node.addEventListener("click", openCompetitorAnalysisModal);
});
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", bindButtons, { once: true });
} else {
bindButtons();
}
window.openCompetitorAnalysisModal = openCompetitorAnalysisModal;
})();