diff --git a/docs/crm.html b/docs/crm.html index b281248..a25fd24 100644 --- a/docs/crm.html +++ b/docs/crm.html @@ -51,6 +51,11 @@ body{font-family:'Inter',sans-serif;background:var(--bg);color:var(--text);displ .tbl-row{display:flex;align-items:center;gap:12px;cursor:pointer;padding:9px 14px;border-top:1px solid var(--bg)} .tbl-row:first-child{border-top:none} .tbl-row:hover{background:#FAFBFC} +.fchip{font-size:11px;font-weight:600;font-family:'Inter';padding:4px 11px;border-radius:7px;border:1px solid var(--border);background:var(--white);color:var(--muted);cursor:pointer;margin-left:6px} +.fchip:hover{border-color:var(--primary);color:var(--primary)} +.fchip.on{background:var(--primary);color:#fff;border-color:var(--primary)} +.sec-h.collapsible{display:flex;align-items:center;gap:10px;cursor:pointer;user-select:none} +.sec-chev{display:inline-block;transition:transform .15s;font-size:11px;color:var(--muted)} /* Pipeline kanban */ .kanban{display:flex;gap:12px;overflow-x:auto;padding-bottom:8px;align-items:flex-start} .kcol{flex:1;min-width:200px;background:#eef0f3;border-radius:12px;padding:10px} @@ -150,6 +155,25 @@ const pipeMap=Object.fromEntries(PIPE.map(p=>[p[0],p])); function esc(s){return (s||"").replace(/&/g,"&").replace(//g,">")} function fmt(s){return esc(s).replace(/\*\*(.+?)\*\*/g,"$1")} function money(n){return (n||0).toLocaleString("ru-RU")+" ₽"} +// ── UI-состояние: сворачивание секций + фильтры (запоминается) ── +const ui=(()=>{try{return JSON.parse(localStorage.getItem('crm_ui'))||{}}catch(e){return{}}})(); +ui.collapsed=ui.collapsed||{revenue:true}; // динамика свёрнута по умолчанию +if(ui.collapsed.revenue===undefined)ui.collapsed.revenue=true; +ui.clientFilter=ui.clientFilter||'all'; +ui.taskFilter=ui.taskFilter||'all'; +function saveUi(){try{localStorage.setItem('crm_ui',JSON.stringify(ui))}catch(e){}} +function toggleSec(k){ui.collapsed[k]=!ui.collapsed[k];saveUi();renderDashboard();} +function setClientFilter(f){ui.clientFilter=f;saveUi();renderDashboard();} +function setTaskFilter(f){ui.taskFilter=f;saveUi();renderDashboard();} +function secHead(k,title,right){ + const col=!!ui.collapsed[k]; + return `
+ + ${title} + ${right?`${right}`:''} +
`; +} +function chips(cur,opts,fn){return opts.map(([k,n])=>``).join('');} async function loadProjects(){ const r=await fetch(`${API}/api/projects`);const d=await r.json();projects=d.projects;renderClientList(); @@ -198,8 +222,21 @@ function renderDashboard(){ ${renderRevenueChart()} ${renderUpcomingTasks()} -
Все клиенты · ${total}
- ${projects.length?`
${projects.map(p=>renderClientRow(p)).join("")}
`:'
Создайте первого клиента
'}`; + ${(()=>{ + const cf=ui.clientFilter; + const pipeOf=p=>(p.crm&&p.crm.pipeline)||'lead'; + let cl=projects; + if(cf==='lead')cl=projects.filter(p=>pipeOf(p)==='lead'); + else if(cf==='active')cl=projects.filter(p=>pipeOf(p)==='active'); + else if(cf==='done')cl=projects.filter(p=>pipeOf(p)==='done'); + else if(cf==='free')cl=projects.filter(p=>((p.crm&&p.crm.billing_type))==='free'); + const cChips=chips(cf,[['all','Все'],['lead','Лиды'],['active','В работе'],['done','Завершён'],['free','Бесплатные']],'setClientFilter'); + const head=secHead('clients',`Все клиенты · ${cl.length}`,cChips); + if(ui.collapsed.clients)return head; + if(!projects.length)return head+'
Создайте первого клиента
'; + if(!cl.length)return head+'
Нет клиентов по фильтру
'; + return head+`
${cl.map(p=>renderClientRow(p)).join("")}
`; + })()}`; } const STAGE_DEFS=[{key:"interview",name:"Интервью"},{key:"methods",name:"Методологии"},{key:"canvas",name:"Стратегия"},{key:"idef0",name:"Функции"},{key:"spec",name:"ТЗ"}]; function clientStages(p){ @@ -263,17 +300,27 @@ function renderRevenueChart(){ const MN=["янв","фев","мар","апр","май","июн","июл","авг","сен","окт","ноя","дек"]; const lbl=k=>{const[y,m]=k.split("-");return MN[+m-1]+" "+y.slice(2)}; const totalRev=keys.reduce((s,k)=>s+months[k],0); - return `
📈 Выручка по месяцам · ${money(totalRev)}
-
${keys.map(k=>`
${(months[k]/1000).toFixed(0)}к
${lbl(k)}
`).join("")}
`; + const head=secHead('revenue',`📈 Выручка по месяцам · ${money(totalRev)}`); + if(ui.collapsed.revenue)return head; + return head+`
${keys.map(k=>`
${(months[k]/1000).toFixed(0)}к
${lbl(k)}
`).join("")}
`; } function renderUpcomingTasks(){ const today=new Date().toISOString().slice(0,10); const all=[]; projects.forEach(p=>(p.tasks||[]).forEach(t=>{if(!t.done)all.push({...t,client:p.client_name,token:p.token})})); all.sort((a,b)=>(a.due||"9999")<(b.due||"9999")?-1:1); - const top=all.slice(0,6); - if(!top.length)return""; - return `
📌 Ближайшие задачи · ${all.length}
${top.map(t=>`
${esc(t.text)}${esc(t.client)}${t.due?`${t.due===today?'сегодня':t.due`:''}
`).join("")}
`; + if(!all.length)return""; + const f=ui.taskFilter; + let list=all; + if(f==='overdue')list=all.filter(t=>t.due&&t.duet.due===today); + const overdueN=all.filter(t=>t.due&&t.due
Нет задач по фильтру
'; + return head+`
${top.map(t=>`
${esc(t.text)}${esc(t.client)}${t.due?`${t.due===today?'сегодня':t.due`:''}
`).join("")}
`; } function renderPipeline(){ document.getElementById("view").innerHTML=`
Воронка продаж
${PIPE.map(([k,name,col])=>{