From 9558c8b423542a7992ba3f5c6bf4ecafaa762284 Mon Sep 17 00:00:00 2001 From: wasrusgen Date: Mon, 1 Jun 2026 23:38:38 +0300 Subject: [PATCH] =?UTF-8?q?feat(crm):=20=D0=B2=D0=BA=D0=BB=D0=B0=D0=B4?= =?UTF-8?q?=D0=BA=D0=B0=20=C2=AB=D0=9E=D1=82=D0=BA=D0=BB=D0=BE=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=C2=BB=20=E2=80=94=20=D1=8D=D1=82=D0=B0=D0=BB?= =?UTF-8?q?=D0=BE=D0=BD=20=D0=95=D0=BB=D0=B5=D0=BD=D1=8B=20=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D1=82=D0=B8=D0=B2=20=D0=B2=D1=8B=D0=B1=D0=BE=D1=80=D0=B0?= =?UTF-8?q?=20=D0=BA=D0=BB=D0=B8=D0=B5=D0=BD=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Оператор при внедрении видит расхождения: решение клиента (внедряем) + рекомендация Елены (флажок) + причина клиента. Бейдж со счётчиком. --- docs/crm.html | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) 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){