ui: правка существующих платежей в реестре (дата/сумма/назначение) + sync со сметой

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
wasrusgen 2026-06-01 08:14:24 +03:00
parent 9b0d0af277
commit a621f301ce

View File

@ -149,7 +149,7 @@ body{font-family:'Inter',sans-serif;background:var(--bg);color:var(--text);displ
<script> <script>
const API=location.hostname.indexOf("wasrusgen1.ru")>=0?"/consulting":"https://claude-83-172-150-111.sslip.io/elena"; const API=location.hostname.indexOf("wasrusgen1.ru")>=0?"/consulting":"https://claude-83-172-150-111.sslip.io/elena";
let projects=[], current=null, state=null, view="dashboard", activeTab="interview", mainTab="deal"; let projects=[], current=null, state=null, view="dashboard", activeTab="interview", mainTab="deal", editPayIdx=-1;
const PIPE=[["lead","Лид","#9ca3af"],["qualified","Квалификация","#3B82F6"],["proposal","Предложение","#8B5CF6"],["active","В работе","#047857"],["done","Завершён","#10B981"]]; const PIPE=[["lead","Лид","#9ca3af"],["qualified","Квалификация","#3B82F6"],["proposal","Предложение","#8B5CF6"],["active","В работе","#047857"],["done","Завершён","#10B981"]];
const pipeMap=Object.fromEntries(PIPE.map(p=>[p[0],p])); const pipeMap=Object.fromEntries(PIPE.map(p=>[p[0],p]));
function esc(s){return (s||"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")} function esc(s){return (s||"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}
@ -194,7 +194,7 @@ async function newClient(){
} }
function setView(v){view=v;current=null;document.getElementById("nav-dash").classList.toggle("active",v==="dashboard");document.getElementById("nav-pipe").classList.toggle("active",v==="pipeline");renderClientList();render();} function setView(v){view=v;current=null;document.getElementById("nav-dash").classList.toggle("active",v==="dashboard");document.getElementById("nav-pipe").classList.toggle("active",v==="pipeline");renderClientList();render();}
async function openClient(token){ async function openClient(token){
current=token;view="client";mainTab="deal";document.querySelectorAll(".nav-item").forEach(n=>n.classList.remove("active")); current=token;view="client";mainTab="deal";editPayIdx=-1;document.querySelectorAll(".nav-item").forEach(n=>n.classList.remove("active"));
const r=await fetch(`${API}/api/project/${token}`);state=await r.json();renderClientList();render(); const r=await fetch(`${API}/api/project/${token}`);state=await r.json();renderClientList();render();
} }
function render(){ function render(){
@ -347,7 +347,7 @@ function renderClient(){
<div id="mainPanel"></div>`; <div id="mainPanel"></div>`;
renderMainPanel(); renderMainPanel();
} }
function setMainTab(t){mainTab=t;renderMainPanel();document.querySelectorAll('.mtab').forEach((b,i)=>b.classList.toggle('active',MAINTABS[i].id===mainTab));} function setMainTab(t){mainTab=t;editPayIdx=-1;renderMainPanel();document.querySelectorAll('.mtab').forEach((b,i)=>b.classList.toggle('active',MAINTABS[i].id===mainTab));}
function renderMainPanel(){ function renderMainPanel(){
const p=document.getElementById("mainPanel");if(!p)return; const p=document.getElementById("mainPanel");if(!p)return;
const crm=state.crm||{pipeline:"lead",deal_amount:0,source:""}; const crm=state.crm||{pipeline:"lead",deal_amount:0,source:""};
@ -628,7 +628,16 @@ function renderPayments(){
${deal>0?`<span style="margin-left:auto;font-size:12px;color:var(--muted)">Сделка ${money(deal)} · Получено ${money(paid)} · <b style="color:${left>0?'#DC2626':'#047857'}">Остаток ${money(left)}</b></span>`:'<span style="margin-left:auto"></span>'} ${deal>0?`<span style="margin-left:auto;font-size:12px;color:var(--muted)">Сделка ${money(deal)} · Получено ${money(paid)} · <b style="color:${left>0?'#DC2626':'#047857'}">Остаток ${money(left)}</b></span>`:'<span style="margin-left:auto"></span>'}
${left>0?`<button class="cp-btn cp-r" style="padding:5px 10px" onclick="remindBalance(${left})">⏰ Задача на остаток</button>`:''} ${left>0?`<button class="cp-btn cp-r" style="padding:5px 10px" onclick="remindBalance(${left})">⏰ Задача на остаток</button>`:''}
${pays.length?`<button class="cp-btn cp-r" style="padding:5px 10px" onclick="exportPayments()">⬇ CSV</button>`:''}</div> ${pays.length?`<button class="cp-btn cp-r" style="padding:5px 10px" onclick="exportPayments()">⬇ CSV</button>`:''}</div>
${pays.map((p,i)=>`<div style="display:flex;align-items:center;gap:10px;padding:7px 0;border-top:1px solid var(--bg)"><span style="font-size:12px;color:var(--muted);min-width:70px">${esc(p.date||'')}</span><span style="flex:1;font-size:13px">${esc(p.note||'Платёж')}</span><span style="font-size:13px;font-weight:700;color:#047857">${money(p.amount)}</span><button onclick="delPayment(${i})" style="border:none;background:none;cursor:pointer;color:#cbd5e1;font-size:14px"></button></div>`).join("")||'<div style="font-size:12px;color:#cbd5e1;padding:4px">Платежей нет</div>'} ${pays.map((p,i)=>i===editPayIdx
?`<div style="display:flex;align-items:center;gap:8px;padding:9px 10px;border-top:1px solid var(--bg);flex-wrap:wrap;background:var(--bg);border-radius:8px;margin-top:4px">
<input type="date" id="epd-${i}" value="${esc(p.date||'')}" style="border:1.5px solid var(--border);border-radius:8px;padding:6px 8px;font-size:13px;font-family:Inter">
<input type="number" id="epa-${i}" value="${p.amount||''}" style="width:100px;border:1.5px solid var(--border);border-radius:8px;padding:6px 9px;font-size:13px;font-family:Inter">
<input id="epn-${i}" value="${esc(p.note||'')}" placeholder="Назначение" style="flex:1;min-width:120px;border:1.5px solid var(--border);border-radius:8px;padding:6px 9px;font-size:13px;font-family:Inter">
${p.stage?`<span style="font-size:10px;color:#9CA3AF;white-space:nowrap">этап: ${esc((CLIENT_STAGES.find(s=>s.key===p.stage)||{}).name||p.stage)}</span>`:''}
<button class="cp-btn cp-a" style="padding:6px 12px" onclick="confirmEditPayment(${i})">Сохранить</button>
<button class="cp-btn cp-r" style="padding:6px 10px" onclick="cancelEditPayment()"></button>
</div>`
:`<div style="display:flex;align-items:center;gap:10px;padding:7px 0;border-top:1px solid var(--bg)"><span style="font-size:12px;color:var(--muted);min-width:70px">${esc(p.date||'')}</span><span style="flex:1;font-size:13px">${esc(p.note||'Платёж')}${p.stage?` <span style="font-size:10px;color:#9CA3AF">· этап</span>`:''}</span><span style="font-size:13px;font-weight:700;color:#047857">${money(p.amount)}</span><span onclick="editPayment(${i})" title="Изменить" style="cursor:pointer;color:#9CA3AF;font-size:13px"></span><button onclick="delPayment(${i})" title="Удалить" style="border:none;background:none;cursor:pointer;color:#cbd5e1;font-size:14px"></button></div>`).join("")||'<div style="font-size:12px;color:#cbd5e1;padding:4px">Платежей нет</div>'}
<div style="display:flex;gap:8px;margin-top:10px;flex-wrap:wrap;align-items:center"> <div style="display:flex;gap:8px;margin-top:10px;flex-wrap:wrap;align-items:center">
<input id="payDate" type="date" value="${new Date().toISOString().slice(0,10)}" style="border:1.5px solid var(--border);border-radius:8px;padding:8px;font-size:13px;font-family:Inter"> <input id="payDate" type="date" value="${new Date().toISOString().slice(0,10)}" style="border:1.5px solid var(--border);border-radius:8px;padding:8px;font-size:13px;font-family:Inter">
<input id="payAmt" type="number" placeholder="Сумма ₽" style="width:110px;border:1.5px solid var(--border);border-radius:8px;padding:8px 11px;font-size:13px;font-family:Inter" onkeydown="if(event.key==='Enter')addPayment()"> <input id="payAmt" type="number" placeholder="Сумма ₽" style="width:110px;border:1.5px solid var(--border);border-radius:8px;padding:8px 11px;font-size:13px;font-family:Inter" onkeydown="if(event.key==='Enter')addPayment()">
@ -645,6 +654,21 @@ async function savePayments(){
await fetch(`${API}/api/project/crm`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token:current,payments:state.crm.payments,stage_payments:state.crm.stage_payments||{}})}); await fetch(`${API}/api/project/crm`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token:current,payments:state.crm.payments,stage_payments:state.crm.stage_payments||{}})});
await loadProjects(); await loadProjects();
} }
function editPayment(i){editPayIdx=i;renderClient();}
function cancelEditPayment(){editPayIdx=-1;renderClient();}
function confirmEditPayment(i){
const p=state.crm.payments[i];if(!p)return;
const amt=+document.getElementById('epa-'+i).value;
if(!amt){alert('Укажите сумму');return;}
const date=document.getElementById('epd-'+i).value||p.date;
const note=document.getElementById('epn-'+i).value;
p.amount=amt;p.date=date;p.note=note||'Платёж';
if(p.stage&&state.crm.stage_payments&&state.crm.stage_payments[p.stage]){
state.crm.stage_payments[p.stage]={amount:amt,date};
}
editPayIdx=-1;
savePayments();renderClient();
}
function addPayment(){ function addPayment(){
const amt=+document.getElementById("payAmt").value;if(!amt){alert("Укажите сумму");return;} const amt=+document.getElementById("payAmt").value;if(!amt){alert("Укажите сумму");return;}
const date=document.getElementById("payDate").value; const date=document.getElementById("payDate").value;