caterium-app/public/core/help-center.js
pavlov346346-source 5e72b23161
Correct support mailbox to support@caterium.ru (#42)
Apply the user's explicit address correction to the PHP recipient, Help links, contact form and tests. Refresh the form URL and PWA cache. Exact-recipient PHP tests and support/Help browser tests passed in isolated run 35575721587. No real mail sent, delivery not claimed. Keep standard main QA and production publication gates unchanged; no auth, database or unrelated feature changes.
2026-09-21 11:04:06 +03:00

80 lines
11 KiB
JavaScript
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.

(()=>{
'use strict';
const version=new URL(document.currentScript.src).searchParams.get('v')||'1';
const url=new URL('../help/knowledge-v1.json',document.currentScript.src);url.searchParams.set('v',version);
const esc=s=>String(s??'').replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
const norm=s=>String(s).toLowerCase().replace(/ё/g,'е');
const BOT={origin:"https://ai-staff-alpha.vercel.app",agent:"e9cc0f28-aaa5-48d4-a020-ae9633988faf"};
let dialog,data,loading=false,returnFocus;
const supportUrl=new URL('support-form.js?v=20260921-support-recipient',document.currentScript.src);
function loadContactForm(){
if(window.CateriumSupportForm){window.CateriumSupportForm.attach(dialog);return;}
if(document.getElementById('ctSupportFormScript'))return;
const script=document.createElement('script');script.id='ctSupportFormScript';script.src=supportUrl.href;
script.onerror=()=>{script.remove();};document.head.append(script);
}
function render(){
if(!data||!dialog)return;
const query=norm(dialog.querySelector('#ctHelpSearch').value).trim(),category=dialog.querySelector('#ctHelpCategory').value;
const terms=query.split(/\s+/).filter(Boolean);
const rows=data.articles.filter(a=>!category||a.category===category).map(a=>({a,score:terms.reduce((n,t)=>n+(norm(a.title+' '+a.keywords).includes(t)?3:0)+(norm(a.steps.join(' ')+' '+a.note).includes(t)?1:0),0)})).filter(r=>!terms.length||r.score>0).sort((a,b)=>b.score-a.score);
dialog.querySelector('#ctHelpStatus').textContent=rows.length?`Инструкций: ${rows.length}`:'Ничего не найдено. Попробуйте «оплата», «PDF», «склад» или откройте все темы.';
dialog.querySelector('#ctHelpResults').innerHTML=rows.map(({a})=>`<details data-article="${esc(a.id)}"><summary>${esc(a.title)}<small>${esc(a.category)}</small></summary><ol>${a.steps.map(s=>`<li>${esc(s)}</li>`).join('')}</ol><p class="ct-help-note">${esc(a.note)}</p><div class="ct-help-related">${(a.related||[]).map(id=>{const r=data.articles.find(x=>x.id===id);return r?`<button type="button" data-help-article="${esc(id)}">${esc(r.title)}</button>`:''}).join('')}</div></details>`).join('');
}
function showGuide(query=''){
switchMode(false);dialog.querySelector('#ctHelpSearch').value=query;dialog.querySelector('#ctHelpCategory').value='';render();dialog.querySelector('#ctHelpSearch').focus();
}
function startBot(){
const box=dialog.querySelector("#ctHelpBot"),start=dialog.querySelector("#ctHelpBotStart");
if(box.querySelector("iframe"))return;
// The chat lives in a sandboxed iframe on the assistant's own origin: nothing from the app (orders, clients, session) is reachable from it, and no request leaves the device until the user asks for the assistant.
const frame=document.createElement("iframe");
frame.src=`${BOT.origin}/embed/${BOT.agent}`;frame.title="ИИ-помощник Caterium";frame.loading="eager";frame.referrerPolicy="no-referrer";
frame.setAttribute("sandbox","allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox");
box.textContent="";box.hidden=false;box.appendChild(frame);start.hidden=true;
}
function switchMode(support){
dialog.querySelector('#ctHelpGuide').hidden=support;dialog.querySelector('#ctHelpSupport').hidden=!support;
dialog.querySelector('#ctHelpGuideTab').setAttribute('aria-pressed',String(!support));dialog.querySelector('#ctHelpSupportTab').setAttribute('aria-pressed',String(support));
}
async function load(){
if(data){render();return;}if(loading)return;loading=true;
const status=dialog.querySelector('#ctHelpStatus'),retry=dialog.querySelector('#ctHelpRetry');status.textContent='Загружаю руководство…';retry.hidden=true;
const controller=new AbortController(),timer=setTimeout(()=>controller.abort(),8000);
try{
const r=await fetch(url,{signal:controller.signal,credentials:'omit'});if(!r.ok)throw new Error('http');const value=await r.json();
if(!Array.isArray(value.articles)||!value.articles.every(a=>typeof a.id==='string'&&typeof a.title==='string'&&typeof a.category==='string'&&Array.isArray(a.steps)&&a.steps.every(s=>typeof s==='string')))throw new Error('format');
data=value;dialog.querySelector('#ctHelpCategory').innerHTML='<option value="">Все темы</option>'+[...new Set(data.articles.map(a=>a.category))].map(c=>`<option>${esc(c)}</option>`).join('');
dialog.querySelector('#ctHelpEdition').textContent=`Руководство · редакция ${data.version}`;render();
}catch(_){status.textContent='Не удалось загрузить руководство. Проверьте соединение и повторите.';retry.hidden=false;}finally{loading=false;clearTimeout(timer);}
}
function ensureDialog(){
if(dialog)return;
dialog=document.createElement('dialog');dialog.id='ctHelpDialog';dialog.setAttribute('aria-labelledby','ctHelpTitle');
dialog.innerHTML=`<div class="ct-help-header"><div><h2 id="ctHelpTitle">Помощь в Caterium</h2><p id="ctHelpEdition">Руководство пользователя и поддержка</p></div><button type="button" id="ctHelpClose" aria-label="Закрыть помощь">×</button></div><div class="ct-help-modes" aria-label="Раздел помощи"><button type="button" id="ctHelpGuideTab" aria-pressed="true">Руководство</button><button type="button" id="ctHelpSupportTab" aria-pressed="false">Поддержка</button></div><div class="ct-help-content"><section id="ctHelpGuide"><div class="ct-help-filters"><label>Что хотите сделать?<input id="ctHelpSearch" type="search" placeholder="Например: оплата, PDF, склад" maxlength="240" autocomplete="off"></label><label>Категория<select id="ctHelpCategory"><option value="">Все темы</option></select></label><button type="button" id="ctHelpReset">Все инструкции</button></div><p id="ctHelpStatus" role="status"></p><button type="button" id="ctHelpRetry" hidden>Повторить загрузку</button><div id="ctHelpResults"></div></section><section id="ctHelpSupport" hidden><span class="ct-help-badge">ИИ-помощник</span><h3>Ответы по работе с приложением</h3><p>Помощник отвечает на вопросы «как это сделать» по руководству Caterium. Он не видит данные вашей компании, но ваш вопрос обрабатывает внешний сервис, поэтому не пишите пароли, коды подтверждения и данные клиентов. Поиск по руководству остаётся на этом устройстве и ничего не отправляет.</p><button type="button" id="ctHelpBotStart" class="ct-help-bot-start">Задать вопрос помощнику</button><div id="ctHelpBot" class="ct-help-bot" hidden></div><div class="ct-help-prompts"><button type="button" data-help-query="создать заказ">Как создать заказ?</button><button type="button" data-help-query="оплата">Как отметить оплату?</button><button type="button" data-help-query="закупки">Как рассчитать закупки?</button><button type="button" data-help-query="синхронизация">Не загружается база</button></div><h3>Если инструкция не помогла</h3><p>Почта поддержки: <a href="mailto:support@caterium.ru">support@caterium.ru</a>. Вкладка «Написать в поддержку» открывает форму обращения.</p><p>Подготовьте название раздела, последовательность действий, точный текст ошибки, устройство и браузер. На снимке экрана закройте лишние телефоны и адреса. Не передавайте пароли и коды подтверждения.</p><button type="button" data-help-query="обращение">Памятка для обращения</button></section></div>`;
document.body.appendChild(dialog);
dialog.querySelector('#ctHelpClose').onclick=()=>dialog.close();
dialog.addEventListener('keydown',e=>{if(e.key==='Escape'){e.preventDefault();e.stopPropagation();dialog.close();}});
dialog.addEventListener('close',()=>{dialog.querySelector('#ctHelpSearch').value='';if(returnFocus?.isConnected)returnFocus.focus({preventScroll:true});});
dialog.querySelector('#ctHelpBotStart').onclick=startBot;dialog.querySelector('#ctHelpGuideTab').onclick=()=>switchMode(false);dialog.querySelector('#ctHelpSupportTab').onclick=()=>switchMode(true);
dialog.querySelector('#ctHelpSearch').oninput=render;dialog.querySelector('#ctHelpCategory').onchange=render;
dialog.querySelector('#ctHelpReset').onclick=()=>showGuide();dialog.querySelector('#ctHelpRetry').onclick=load;
dialog.addEventListener('click',e=>{
const q=e.target.closest('[data-help-query]');if(q){showGuide(q.dataset.helpQuery);return;}
const a=e.target.closest('[data-help-article]');if(a&&data){showGuide();const target=[...dialog.querySelectorAll('details')].find(x=>x.dataset.article===a.dataset.helpArticle);if(target){target.open=true;target.scrollIntoView({block:'nearest'});target.querySelector('summary').focus();}}
});
}
function open(trigger){ensureDialog();loadContactForm();dialog.querySelector('#ctContactSection')?.setAttribute('hidden','');dialog.querySelector('#ctContactTab')?.setAttribute('aria-pressed','false');returnFocus=trigger?.currentTarget||trigger||document.activeElement;switchMode(false);if(!dialog.open)dialog.showModal();load();dialog.querySelector('#ctHelpSearch').focus();}
function mount(){
const nav=document.querySelector('header nav');
if(nav&&!document.getElementById('ctHelpNav')){const b=document.createElement('button');b.id='ctHelpNav';b.type='button';b.dataset.navLabel='Помощь';b.textContent='Помощь';b.onclick=open;nav.appendChild(b);window.sunRegisterNavButton?.({button:b,group:'management',navLabel:'Помощь'});}
const gate=document.getElementById('sunCloudAuthGateV3');
if(gate&&!gate.querySelector('.ct-help-login')){const b=document.createElement('button');b.type='button';b.className='ct-help-login';b.textContent='Помощь со входом';b.onclick=()=>{open(b);showGuide('вход');};(gate.querySelector('.sun-cloud-auth-card')||gate).appendChild(b);}
}
function boot(){mount();let frame=0;new MutationObserver(()=>{if(frame)return;frame=requestAnimationFrame(()=>{frame=0;mount();});}).observe(document.body,{childList:true,subtree:true});
const context=()=>`${window.SunCloudV2?.getSession?.()?.user?.id||''}:${window.SunCloudV2?.getWorkspace?.()?.id||''}`;
let current=context();window.addEventListener('sun:cloud-permissions-changed',()=>{const next=context();if(next!==current&&dialog?.open)dialog.close();current=next;});
}
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',boot,{once:true});else boot();
})();