"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; })();