mirror of
https://github.com/wasrusgen/wasrusgen1-crm.git
synced 2026-06-03 16:44:46 +00:00
feat(дашборд): поиск по клиентам + сортировка (имя/сумма/этап/статус)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d8036dbdb3
commit
c7c91d9dbe
@ -165,6 +165,9 @@ function saveUi(){try{localStorage.setItem('crm_ui',JSON.stringify(ui))}catch(e)
|
|||||||
function toggleSec(k){ui.collapsed[k]=!ui.collapsed[k];saveUi();renderDashboard();}
|
function toggleSec(k){ui.collapsed[k]=!ui.collapsed[k];saveUi();renderDashboard();}
|
||||||
function setClientFilter(f){ui.clientFilter=f;saveUi();renderDashboard();}
|
function setClientFilter(f){ui.clientFilter=f;saveUi();renderDashboard();}
|
||||||
function setTaskFilter(f){ui.taskFilter=f;saveUi();renderDashboard();}
|
function setTaskFilter(f){ui.taskFilter=f;saveUi();renderDashboard();}
|
||||||
|
function setClientSearch(v){ui.clientSearch=v;renderDashboard();const el=document.getElementById('clientSearch');if(el){el.focus();const L=el.value.length;try{el.setSelectionRange(L,L);}catch(e){}}}
|
||||||
|
function setClientSort(k){if(ui.clientSort===k)ui.clientSortDir=(ui.clientSortDir||1)*-1;else{ui.clientSort=k;ui.clientSortDir=1;}saveUi();renderDashboard();}
|
||||||
|
function projStageCnt(p){return [p.msg_count>0,!!p.has_selection,!!p.has_canvas,!!p.has_idef0,!!p.has_spec].filter(Boolean).length;}
|
||||||
function secHead(k,title,right){
|
function secHead(k,title,right){
|
||||||
const col=!!ui.collapsed[k];
|
const col=!!ui.collapsed[k];
|
||||||
return `<div class="sec-h collapsible" onclick="toggleSec('${k}')">
|
return `<div class="sec-h collapsible" onclick="toggleSec('${k}')">
|
||||||
@ -230,12 +233,28 @@ function renderDashboard(){
|
|||||||
else if(cf==='active')cl=projects.filter(p=>pipeOf(p)==='active');
|
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==='done')cl=projects.filter(p=>pipeOf(p)==='done');
|
||||||
else if(cf==='free')cl=projects.filter(p=>((p.crm&&p.crm.billing_type))==='free');
|
else if(cf==='free')cl=projects.filter(p=>((p.crm&&p.crm.billing_type))==='free');
|
||||||
|
const q=(ui.clientSearch||'').toLowerCase().trim();
|
||||||
|
if(q)cl=cl.filter(p=>(((p.client_name||'')+' '+(p.niche||'')).toLowerCase().indexOf(q)>=0));
|
||||||
|
const sk=ui.clientSort||'name', dir=ui.clientSortDir||1;
|
||||||
|
const pipeOrder={lead:0,qualified:1,proposal:2,active:3,done:4};
|
||||||
|
cl=cl.slice().sort((a,b)=>{let av,bv;
|
||||||
|
if(sk==='amount'){av=(a.crm&&a.crm.deal_amount)||0;bv=(b.crm&&b.crm.deal_amount)||0;}
|
||||||
|
else if(sk==='stage'){av=projStageCnt(a);bv=projStageCnt(b);}
|
||||||
|
else if(sk==='status'){av=pipeOrder[pipeOf(a)]||0;bv=pipeOrder[pipeOf(b)]||0;}
|
||||||
|
else {av=(a.client_name||'').toLowerCase();bv=(b.client_name||'').toLowerCase();}
|
||||||
|
if(av<bv)return -dir;if(av>bv)return dir;return 0;});
|
||||||
const cChips=chips(cf,[['all','Все'],['lead','Лиды'],['active','В работе'],['done','Завершён'],['free','Бесплатные']],'setClientFilter');
|
const cChips=chips(cf,[['all','Все'],['lead','Лиды'],['active','В работе'],['done','Завершён'],['free','Бесплатные']],'setClientFilter');
|
||||||
const head=secHead('clients',`Все клиенты · ${cl.length}`,cChips);
|
const head=secHead('clients',`Все клиенты · ${cl.length}`,cChips);
|
||||||
if(ui.collapsed.clients)return head;
|
if(ui.collapsed.clients)return head;
|
||||||
if(!projects.length)return head+'<div class="empty">Создайте первого клиента</div>';
|
if(!projects.length)return head+'<div class="empty">Создайте первого клиента</div>';
|
||||||
if(!cl.length)return head+'<div class="tbl"><div class="cl-row" style="color:#9CA3AF;font-size:12px;justify-content:center">Нет клиентов по фильтру</div></div>';
|
const arrow=k=>ui.clientSort===k?(dir<0?' ↓':' ↑'):'';
|
||||||
return head+`<div class="tbl">${cl.map(p=>renderClientRow(p)).join("")}</div>`;
|
const bar=`<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-bottom:10px">
|
||||||
|
<input id="clientSearch" value="${esc(ui.clientSearch||'')}" oninput="setClientSearch(this.value)" placeholder="🔍 Поиск по имени или нише…" style="flex:1;min-width:170px;border:1.5px solid var(--border);border-radius:8px;padding:7px 11px;font-size:13px;font-family:Inter">
|
||||||
|
<span style="font-size:11px;color:var(--muted)">Сортировка:</span>
|
||||||
|
${[['name','Имя'],['amount','Сумма'],['stage','Этап'],['status','Статус']].map(([k,n])=>`<button class="fchip ${ui.clientSort===k?'on':''}" onclick="setClientSort('${k}')">${n}${arrow(k)}</button>`).join('')}
|
||||||
|
</div>`;
|
||||||
|
if(!cl.length)return head+bar+'<div class="tbl"><div class="cl-row" style="color:#9CA3AF;font-size:12px;justify-content:center">Ничего не найдено</div></div>';
|
||||||
|
return head+bar+`<div class="tbl">${cl.map(p=>renderClientRow(p)).join("")}</div>`;
|
||||||
})()}`;
|
})()}`;
|
||||||
}
|
}
|
||||||
const STAGE_DEFS=[{key:"interview",name:"Интервью"},{key:"methods",name:"Методологии"},{key:"canvas",name:"Стратегия"},{key:"idef0",name:"Функции"},{key:"spec",name:"ТЗ"}];
|
const STAGE_DEFS=[{key:"interview",name:"Интервью"},{key:"methods",name:"Методологии"},{key:"canvas",name:"Стратегия"},{key:"idef0",name:"Функции"},{key:"spec",name:"ТЗ"}];
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user