diff --git a/docs/crm.html b/docs/crm.html index ff6771a..1d4b760 100644 --- a/docs/crm.html +++ b/docs/crm.html @@ -414,14 +414,15 @@ async function kDrop(e,pipe){ await loadProjects();renderPipeline(); } -const MAINTABS=[{id:"deal",name:"Сделка",icon:"📇"},{id:"pricing",name:"Ценообразование",icon:"💰"},{id:"payments",name:"Платежи",icon:"💳"},{id:"tasks",name:"Задачи",icon:"📌"},{id:"analysis",name:"Анализ",icon:"📊"}]; +const MAINTABS=[{id:"deal",name:"Сделка",icon:"📇"},{id:"pricing",name:"Ценообразование",icon:"💰"},{id:"payments",name:"Платежи",icon:"💳"},{id:"tasks",name:"Задачи",icon:"📌"},{id:"analysis",name:"Анализ",icon:"📊"},{id:"deviations",name:"Отклонения",icon:"⚠️"}]; function renderClient(){ const crm=state.crm||{pipeline:"lead",deal_amount:0,paid_amount:0,contact:"",source:"",note:""}; const billing=crm.billing_type||"paid"; const deal=crm.deal_amount||0, pays=crm.payments||[], paid=pays.reduce((s,p)=>s+(p.amount||0),0), left=deal-paid; const today=new Date().toISOString().slice(0,10); const overdue=(state.tasks||[]).filter(t=>!t.done&&t.due&&t.due{if(id==="payments"&&deal>0&&left>0)return `${money(left)}`;if(id==="tasks"&&overdue)return `${overdue}`;return ''}; + const devN=(state.deviations||[]).length; + const badge=id=>{if(id==="payments"&&deal>0&&left>0)return `${money(left)}`;if(id==="tasks"&&overdue)return `${overdue}`;if(id==="deviations"&&devN)return `${devN}`;return ''}; document.getElementById("view").innerHTML=`
${esc((state.client_name||'?')[0])}
${esc(state.client_name||'Без имени')}
${esc(state.niche||'')} · ${state.messages.length} сообщений
@@ -433,6 +434,35 @@ function renderClient(){ renderMainPanel(); } function setMainTab(t){mainTab=t;editPayIdx=-1;renderMainPanel();document.querySelectorAll('.mtab').forEach((b,i)=>b.classList.toggle('active',MAINTABS[i].id===mainTab));} +const DEV_STAGE={canvas:"📊 Стратегия",idef0:"🔧 Функции",spec:"📋 ТЗ",documents:"📁 Документы",methods:"🎯 Методологии",interview:"💬 Интервью"}; +function renderDeviations(){ + const dev=state.deviations||[]; + if(!dev.length)return `
+
+
Отклонений нет
+
Клиент пока не настаивал на изменениях. Когда в кабинете на этапах 3–5 он скажет Елене «мне так удобно» — расхождение появится здесь: эталон против выбора клиента.
`; + const fmtD=d=>{if(!d)return'';const a=String(d).slice(0,10).split('-');return a.length===3?a[2]+'.'+a[1]+'.'+a[0].slice(2):'';}; + return `
При внедрении реализуем решение клиента (он за него отвечает), но рекомендация Елены остаётся флажком — это и подстраховка, и карта что дотянуть позже.
`+ + dev.map(d=>`
+
+ ${DEV_STAGE[d.stage]||esc(d.stage||'этап')} + ${esc(d.node||'')} + ${d.at?`${fmtD(d.at)}`:''} +
+
+ +
Решение клиента · внедряем
${esc(d.client_choice||'')}
+
+
+ ⚠️ +
Елена рекомендовала
${esc(d.elena_rec||'')}
+
+
+ 💬 +
Причина клиента
${esc(d.reason||'')}
+
+
`).join(''); +} function renderMainPanel(){ const p=document.getElementById("mainPanel");if(!p)return; const crm=state.crm||{pipeline:"lead",deal_amount:0,source:""}; @@ -485,6 +515,7 @@ function renderMainPanel(){ else if(mainTab==="payments"){p.innerHTML=`
`;renderPaymentPlan();renderPayments();} else if(mainTab==="tasks"){p.innerHTML=`
`;renderTasks();} else if(mainTab==="analysis"){p.innerHTML=`
${TABS.map(t=>`
${t.icon} ${t.name}
`).join("")}
`;renderTab();} + else if(mainTab==="deviations"){p.innerHTML=renderDeviations();} } function inviteTelegram(){const url=`https://t.me/wasrusgen1_consulting_bot?start=${current}`;navigator.clipboard.writeText(url).then(()=>alert("Ссылка для Telegram скопирована:\n\n"+url+"\n\nКлиент откроет кабинет прямо в Telegram.")).catch(()=>prompt("Ссылка для Telegram:",url));} async function setBilling(t){