feat: product/service field on deal

- New Deal.product field (e.g. "CRM-внедрение").
- Shown in manager table, РОП table, broadcast recipients.
- {product} added to broadcast template variables.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
askhat 2026-05-19 14:53:29 +05:00
parent 64cadc0ece
commit 94f5d6a56b
2 changed files with 11 additions and 3 deletions

View File

@ -59,6 +59,7 @@ function renderManagerView(){
<td> <td>
<b>${escapeHtml(d.company)}</b> <b>${escapeHtml(d.company)}</b>
${d.managerAway ? '<span class="away-badge">отсутствует</span>' : ''} ${d.managerAway ? '<span class="away-badge">отсутствует</span>' : ''}
${d.product ? `<div style="font-size:12px;color:#444">📦 ${escapeHtml(d.product)}</div>` : ''}
${d.tag ? `<div style="font-size:11px;color:#666">${escapeHtml(d.tag)}</div>` : ''} ${d.tag ? `<div style="font-size:11px;color:#666">${escapeHtml(d.tag)}</div>` : ''}
</td> </td>
<td>${escapeHtml(d.stage)}</td> <td>${escapeHtml(d.stage)}</td>
@ -111,7 +112,10 @@ function renderRopView(){
tbody.innerHTML = all.map(({d, days, color}) => ` tbody.innerHTML = all.map(({d, days, color}) => `
<tr class="row-${color} ${d.managerAway?'row-away':''}"> <tr class="row-${color} ${d.managerAway?'row-away':''}">
<td>${escapeHtml(d.manager)}${d.managerAway ? ' <span class="away-badge">отсут.</span>' : ''}</td> <td>${escapeHtml(d.manager)}${d.managerAway ? ' <span class="away-badge">отсут.</span>' : ''}</td>
<td>${escapeHtml(d.company)}</td> <td>
${escapeHtml(d.company)}
${d.product ? `<div style="font-size:11px;color:#666">📦 ${escapeHtml(d.product)}</div>` : ''}
</td>
<td>${escapeHtml(d.stage)}</td> <td>${escapeHtml(d.stage)}</td>
<td><span class="days-badge ${days >= 2 ? 'urgent' : ''}">${days}</span></td> <td><span class="days-badge ${days >= 2 ? 'urgent' : ''}">${days}</span></td>
<td>${escapeHtml(d.currentStatus || "—")}</td> <td>${escapeHtml(d.currentStatus || "—")}</td>
@ -147,6 +151,7 @@ function openDealModal(id){
if(!d) return; if(!d) return;
form.id.value = d.id; form.id.value = d.id;
form.company.value = d.company; form.company.value = d.company;
form.product.value = d.product || "";
form.manager.value = d.manager; form.manager.value = d.manager;
form.stage.value = d.stage; form.stage.value = d.stage;
form.tag.value = d.tag || ""; form.tag.value = d.tag || "";
@ -198,6 +203,7 @@ function bindEvents(){
const deal = existing || { id: null, history: [] }; const deal = existing || { id: null, history: [] };
deal.company = fd.get("company").trim(); deal.company = fd.get("company").trim();
deal.product = (fd.get("product") || "").trim();
deal.manager = fd.get("manager").trim(); deal.manager = fd.get("manager").trim();
deal.stage = fd.get("stage"); deal.stage = fd.get("stage");
deal.tag = fd.get("tag") || ""; deal.tag = fd.get("tag") || "";
@ -310,6 +316,7 @@ function bindEvents(){
function renderTemplate(tpl, deal){ function renderTemplate(tpl, deal){
return tpl return tpl
.replace(/\{company\}/g, deal.company || "") .replace(/\{company\}/g, deal.company || "")
.replace(/\{product\}/g, deal.product || "")
.replace(/\{manager\}/g, deal.manager || "") .replace(/\{manager\}/g, deal.manager || "")
.replace(/\{stage\}/g, deal.stage || "") .replace(/\{stage\}/g, deal.stage || "")
.replace(/\{status\}/g, deal.currentStatus || "—") .replace(/\{status\}/g, deal.currentStatus || "—")
@ -328,7 +335,7 @@ function openBroadcast(){
const has = !!(d.clientContact && d.clientContact.trim()); const has = !!(d.clientContact && d.clientContact.trim());
return `<label style="display:flex;gap:8px;align-items:center;padding:4px 0;${has?'':'opacity:.5'}"> return `<label style="display:flex;gap:8px;align-items:center;padding:4px 0;${has?'':'opacity:.5'}">
<input type="checkbox" data-id="${d.id}" ${has?'':'disabled'}> <input type="checkbox" data-id="${d.id}" ${has?'':'disabled'}>
<span><b>${escapeHtml(d.company)}</b> — ${escapeHtml(d.manager)} · ${escapeHtml(d.stage)} <span><b>${escapeHtml(d.company)}</b>${d.product?` <span style="color:#666">(${escapeHtml(d.product)})</span>`:''} — ${escapeHtml(d.manager)} · ${escapeHtml(d.stage)}
${has ? `<span style="color:var(--muted);font-size:12px"> → ${escapeHtml(d.clientContact)}</span>` : `<span style="color:var(--danger);font-size:12px"> (нет контакта)</span>`} ${has ? `<span style="color:var(--muted);font-size:12px"> → ${escapeHtml(d.clientContact)}</span>` : `<span style="color:var(--danger);font-size:12px"> (нет контакта)</span>`}
</span> </span>
</label>`; </label>`;

View File

@ -136,6 +136,7 @@ td.status-cell textarea:focus{background:#fff;outline:2px solid var(--primary)}
<form id="deal-form"> <form id="deal-form">
<input type="hidden" name="id"> <input type="hidden" name="id">
<label>Компания <input name="company" required></label> <label>Компания <input name="company" required></label>
<label>Название продукта/услуги <input name="product" placeholder="например: CRM-внедрение"></label>
<label>Менеджер <label>Менеджер
<input name="manager" list="manager-datalist" required> <input name="manager" list="manager-datalist" required>
<datalist id="manager-datalist"></datalist> <datalist id="manager-datalist"></datalist>
@ -207,7 +208,7 @@ td.status-cell textarea:focus{background:#fff;outline:2px solid var(--primary)}
С уважением, {manager}</textarea> С уважением, {manager}</textarea>
</label> </label>
<p class="hint">Переменные: <code>{company}</code>, <code>{manager}</code>, <code>{stage}</code>, <code>{status}</code>, <code>{contact}</code></p> <p class="hint">Переменные: <code>{company}</code>, <code>{product}</code>, <code>{manager}</code>, <code>{stage}</code>, <code>{status}</code>, <code>{contact}</code></p>
<div style="display:flex;gap:12px;align-items:center;margin:8px 0"> <div style="display:flex;gap:12px;align-items:center;margin:8px 0">
<label><input type="checkbox" id="bc-select-all"> Выбрать всех с контактом</label> <label><input type="checkbox" id="bc-select-all"> Выбрать всех с контактом</label>
<span id="bc-count" style="color:var(--muted);font-size:13px"></span> <span id="bc-count" style="color:var(--muted);font-size:13px"></span>