/* ===== HOTPATCH Sprint 9+10: apply resume/cover fields + universal AI intake ===== */ /* ===== Sprint 9: поля «Резюме» + «Сопроводительное» на отклике + связь с генератором письма ===== */ /* Варианты резюме под вакансию (совпадают с ролями генератора письма) */ var RESUME_OPTS=["","Head of Digital / Performance","Senior / Performance-маркетолог","Руководитель интернет-маркетинга","CMO / Директор по маркетингу"]; /* Инжект двух полей в карточку отклика. Всегда видимы, сохраняются на изменении. */ function applyExtraFields(id){ var dlg=document.getElementById("dialog"); if(!dlg)return; var grid=dlg.querySelector("#editscope .grid")||dlg.querySelector(".grid"); if(!grid)return; if(grid.querySelector('[data-xk="resume"]'))return; var rec=(typeof recOf==="function")?recOf("applies",id):null; if(!rec)return; function saveField(k,v){ rec[k]=v; try{ if(typeof save==="function")save(); }catch(e){} } /* Резюме под вакансию */ var f1=document.createElement("div"); f1.className="f fw"; f1.setAttribute("data-xrow","1"); var lab1=document.createElement("div"); lab1.className="lab"; lab1.textContent="Резюме под вакансию"; f1.appendChild(lab1); var sel=document.createElement("select"); sel.setAttribute("data-xk","resume"); RESUME_OPTS.forEach(function(o){ var op=document.createElement("option"); op.value=o; op.textContent=o||"— не выбрано —"; if((rec.resume||"")===o)op.selected=true; sel.appendChild(op); }); sel.addEventListener("change",function(){ saveField("resume",sel.value); }); f1.appendChild(sel); grid.appendChild(f1); /* Сопроводительное письмо */ var f2=document.createElement("div"); f2.className="f fw"; f2.setAttribute("data-xrow","1"); var lab2=document.createElement("div"); lab2.className="lab"; lab2.textContent="Сопроводительное письмо (что отправил)"; f2.appendChild(lab2); var ta=document.createElement("textarea"); ta.setAttribute("data-xk","cover"); ta.style.minHeight="120px"; ta.style.width="100%"; ta.value=rec.cover||""; ta.addEventListener("input",function(){ saveField("cover",ta.value); }); f2.appendChild(ta); grid.appendChild(f2); } /* Врезаем инжект в openEdit для откликов (после enhanceCard, чтобы поля осели внизу и не прятались) */ var _openEdit_i=openEdit; openEdit=function(coll,id){ _openEdit_i(coll,id); if(coll==="applies"){ window.__coverApplyId=id; setTimeout(function(){ try{ applyExtraFields(id); }catch(e){} },170); } else { window.__coverApplyId=null; } }; /* «Вставить в отклик»: кладём сгенерированное письмо в поле cover открытого отклика + фиксируем резюме */ function coverSaveToApply(){ var id=window.__coverApplyId; if(!id){ toast("Открой отклик, потом жми письмо"); return; } var rec=(typeof recOf==="function")?recOf("applies",id):null; if(!rec){ toast("Отклик не найден"); return; } var out=document.getElementById("covout"); var txt=out?out.value.trim():""; if(!txt){ toast("Сначала сгенерируй письмо"); return; } rec.cover=txt; var rs=document.getElementById("covrole"); var role=rs?rs.value:""; if(role && role!=="auto"){ rec.resume=({Head:"Head of Digital / Performance",Senior:"Senior / Performance-маркетолог",Lead:"Руководитель интернет-маркетинга",CMO:"CMO / Директор по маркетингу"})[role]||rec.resume; } try{ if(typeof save==="function")save(); }catch(e){} var m=document.getElementById("covmodal"); if(m)m.style.display="none"; toast("Письмо сохранено в отклик"); try{ openEdit("applies",id); }catch(e){} } /* Добавляем кнопку «Вставить в отклик» в модалку письма (после её создания) */ var _coverOpen_i=(typeof coverOpen==="function")?coverOpen:null; if(_coverOpen_i && !_coverOpen_i.__applybtn){ coverOpen=function(){ var r=_coverOpen_i.apply(this,arguments); setTimeout(function(){ var modal=document.getElementById("covmodal"); if(!modal)return; if(modal.querySelector("#covsaveapply"))return; /* найдём панель с кнопкой Скопировать */ var copyBtn=null; var btns=modal.querySelectorAll("button"); [].slice.call(btns).forEach(function(b){ if(/Скопировать/.test(b.textContent||""))copyBtn=b; }); if(copyBtn && copyBtn.parentNode){ var b=document.createElement("button"); b.className="btn"; b.id="covsaveapply"; b.textContent="Вставить в отклик"; b.onclick=coverSaveToApply; if(!window.__coverApplyId)b.style.display="none"; copyBtn.parentNode.insertBefore(b,copyBtn.nextSibling); } },60); return r; }; coverOpen.__applybtn=1; } /* ===== Sprint 10: универсальный ИИ-вход «Новый контакт» ===== Использует рабочий серверный ai_parse. Тип (отклик/клиент) и уточняющие вопросы — на клиенте. Ничего не создаётся без подтверждения Александра. */ var INTAKE_SRC=["Звонок","HH.ru","Сайт","Telegram","Рекомендация","Исходящий контакт","Другое"]; function intakeEnsureModal(){ if(document.getElementById("inmodal"))return; var d=document.createElement("div");d.id="inmodal"; d.style.cssText="position:fixed;inset:0;background:rgba(0,0,0,.55);display:none;z-index:10004;align-items:flex-start;justify-content:center;overflow:auto;padding:40px 16px"; var srcopts=INTAKE_SRC.map(function(s){return '';}).join(""); d.innerHTML='
'+ '
Новый контакт — разобрать через ИИ
'+ '
Опиши свободно, откуда контакт и что за запрос: расшифровку звонка, письмо, вакансию с hh, заявку с сайта. ИИ вытащит поля, определит отклик это или клиент и задаст уточняющие вопросы. Ничего не создастся без твоей проверки.
'+ '
Источник:'+ ''+ 'Тип:'+ ''+ '
'+ ''+ '
'+ '
'+ '
'; document.body.appendChild(d); } function intakeOpen(){ if(!window.__key){toast("Сначала войдите");return;} intakeEnsureModal(); var m=document.getElementById("inmodal"); m.style.display="flex"; var q=document.getElementById("inq"); if(q)q.innerHTML=""; var st=document.getElementById("instatus"); if(st)st.textContent=""; var h=document.getElementById("inkindhint"); if(h)h.textContent=""; var t=document.getElementById("intext"); if(t)t.focus(); window.__intakeFields=null; window.__intakeColl=null; } function intakeClose(){var m=document.getElementById("inmodal");if(m)m.style.display="none";} /* эвристика типа контакта */ function intakeGuessKind(text,src){ var t=(text||"").toLowerCase(); var jobHints=/(ваканси|резюме|отклик|должност|обязанност|требовани|мы предлагаем|график работ|оформлени|оклад|з\/п|заработн|штат|full-?time|hh\.ru|хедхантер)/; var clientHints=/(хочу|нужен маркетолог|нужен директор|заявк|бюджет на рекламу|продвижен|подрядчик|реклама не работает|лиды дорог|привести клиент|запустить рекламу|аудит)/; if(src==="HH.ru")return "applies"; var j=jobHints.test(t), c=clientHints.test(t); if(j&&!c)return "applies"; if(c&&!j)return "leads"; return "leads"; } /* какие уточняющие вопросы задать по пустым важным полям */ function intakeQuestions(coll,f){ var qs=[]; if(coll==="applies"){ if(!f.company)qs.push("Какая компания / работодатель?"); if(!f.vacancy&&!f.want)qs.push("Какая вакансия или позиция?"); if(!f.link)qs.push("Есть ссылка на вакансию?"); qs.push("Какой формат интересен: Fractional CMO, штат, проект?"); } else { if(!f.company)qs.push("Как называется компания / клиент?"); if(!f.sphere)qs.push("Какая ниша или сфера бизнеса?"); if(!f.want)qs.push("Что именно хочет — какая задача?"); if(!f.budget)qs.push("Есть понимание бюджета на маркетинг / рекламу?"); if(!f.contact)qs.push("Контакт: имя, телеграм или телефон?"); } return qs.slice(0,5); } function intakeRun(){ var t=document.getElementById("intext"); var text=t?t.value.trim():""; var src=(document.getElementById("insrc")||{}).value||"Другое"; var kindSel=(document.getElementById("inkind")||{}).value||"auto"; var st=document.getElementById("instatus"); var b=document.getElementById("ingo"); if(!text){if(st)st.textContent="Опиши контакт";return;} if(!window.__key){if(st)st.textContent="Войди заново";return;} var answers=""; var qwrap=document.getElementById("inq"); if(qwrap){ [].slice.call(qwrap.querySelectorAll("[data-q]")).forEach(function(inp){ var qq=inp.getAttribute("data-q"); var av=inp.value.trim(); if(av)answers+="\n"+qq+" — "+av; }); } var dialog="Источник контакта: "+src+".\n"+text+(answers?("\n\nУточнения:"+answers):""); if(b)b.disabled=true; if(st)st.textContent="Разбираю… (5–15 сек)"; apiPost({action:"ai_parse",key:window.__key,dialog:dialog}).then(function(r){ if(b)b.disabled=false; if(!r||!r.ok||!r.fields){ if(r&&r.error==="nokey"){ if(st)st.textContent="ИИ-ключ не настроен (окно «AI · Из переписки» → сохранить ключ)."; } else if(r&&r.error==="auth"){ if(st)st.textContent="Сессия истекла, войди заново."; } else { if(st)st.textContent="Не разобралось. Добавь деталей и попробуй ещё."; } return; } var f=r.fields; var coll=(kindSel!=="auto")?kindSel:intakeGuessKind(text,src); window.__intakeFields=f; window.__intakeColl=coll; window.__intakeSrc=src; var h=document.getElementById("inkindhint"); if(h)h.textContent="→ "+(coll==="applies"?"отклик на вакансию":"клиент / лид"); var qs=intakeQuestions(coll,f); var already=qwrap?qwrap.querySelectorAll("[data-q]").length:0; var qwrap2=document.getElementById("inq"); if(qs.length && already===0){ var html='
Уточни, чтобы карточка была полной (любой вопрос можно пропустить):
'; qs.forEach(function(q){ html+='
'+esc(q)+'
'; }); html+='
'; if(qwrap2)qwrap2.innerHTML=html; if(st)st.textContent="Ответь на вопросы или создавай как есть."; } else { if(qwrap2)qwrap2.innerHTML='
'; if(st)st.textContent="Готово. Проверь и создай карточку."; } }).catch(function(){ if(b)b.disabled=false; if(st)st.textContent="Нет связи с сервером"; }); } function intakeCreate(){ var coll=window.__intakeColl||"leads"; var f=window.__intakeFields||{}; var src=window.__intakeSrc||"Другое"; var st=document.getElementById("instatus"); var id=(coll==="applies"?"in":"il")+Date.now(); var rec={id:id, date:(typeof today==="function"?today():"")}; if(coll==="applies"){ rec.company=f.company||"Без названия"; rec.vacancy=f.vacancy||f.want||""; rec.source=src; rec.channel=(src==="HH.ru"?"HH.ru":(src==="Telegram"?"Telegram":"Другой")); rec.status="Новый"; if(f.concl)rec.reason=f.concl; if(f.budget)rec.budget=f.budget; } else { ["company","contact","sphere","want","budget","stage","next","nextdate","agreed","pain","quote","concl"].forEach(function(k){ if(f[k]!=null&&f[k]!=="")rec[k]=f[k]; }); if(!rec.company)rec.company="Без названия"; rec.source=src; if(!rec.stage)rec.stage="Новый контакт"; if(!rec.target)rec.target="Неясно"; } try{ db[coll].unshift(rec); if(typeof save==="function")save(); }catch(e){ if(st)st.textContent="Не удалось сохранить"; return; } intakeClose(); try{ TAB=coll; if(typeof renderTabs==="function")renderTabs(); if(typeof render==="function")render(); }catch(e){} setTimeout(function(){ try{ openEdit(coll,id); }catch(e){} toast("Карточка создана — проверь и допиши"); },80); } /* Кнопка в шапке */ (function(){ function inject(){ var tools=document.querySelector("header.top .tools"); if(tools && !document.getElementById("intakebtn")){ var b=document.createElement("button"); b.className="btn sm primary"; b.id="intakebtn"; b.textContent="+ Новый контакт"; b.onclick=intakeOpen; tools.insertBefore(b, tools.firstChild); } } inject(); setTimeout(inject,300); })(); /* HOTPATCH Sprint 11: mobile card fix */ (function(){ if(document.getElementById("crmmobfix2"))return; var st=document.createElement("style");st.id="crmmobfix2"; st.textContent= "@media(max-width:680px){"+ "#modal{background:var(--bg,#0d1117)!important;padding:0!important;align-items:flex-start!important}"+ "#modal .dialog,#modal .dialog.cardx{background:var(--card,#1c2128)!important;width:100%!important;max-width:100%!important;min-height:100vh!important;margin:0!important;border-radius:0!important;box-shadow:none!important;padding:14px 14px 40px!important}"+ "#modal .cardx .cardhead{position:static!important;background:transparent!important;border-bottom:1px solid var(--line,#2a2f37)!important}"+ "#modal .cardx .grid{display:block!important}"+ "#modal .cardx .grid .f{display:block!important;margin:0 0 14px 0!important}"+ "#modal .cardx .grid .f.hid{display:none!important}"+ "#modal .cardx .grid .f .lab{display:block!important;margin-bottom:4px!important}"+ "#modal .cardx .grid .f input,#modal .cardx .grid .f select,#modal .cardx .grid .f textarea{width:100%!important;box-sizing:border-box!important}"+ "#modal .cardx .actbar{flex-wrap:wrap!important;gap:6px!important}"+ "#modal .cardx .actbar .btn{font-size:12px!important;padding:8px 10px!important}"+ "}"; document.head.appendChild(st); })(); /* ===== HOTPATCH Sprint 13: clickable rows ===== */ /* ===== Sprint 13: строки списка кликабельны целиком, убрать кнопку «Открыть» ===== */ (function(){ var list=document.getElementById("list"); if(!list)return; var obs; function clean(){ if(obs)obs.disconnect(); try{ var rows=list.querySelectorAll("tbody tr"); [].slice.call(rows).forEach(function(tr){ var btn=null; [].slice.call(tr.querySelectorAll("button,a")).forEach(function(b){ if(/^\s*Открыть\s*$/.test((b.textContent||"").trim()))btn=b; }); if(btn){ var oc=btn.getAttribute("onclick"); if(oc && !tr.getAttribute("onclick")) tr.setAttribute("onclick",oc); tr.style.cursor="pointer"; var cell=btn.parentNode; if(cell && cell.removeChild) cell.removeChild(btn); if(cell && cell.tagName==="TD" && !cell.textContent.trim() && !cell.querySelector("*")) cell.parentNode.removeChild(cell); } [].slice.call(tr.querySelectorAll("select,input,button,a,textarea")).forEach(function(c){ if(!c.__np){ c.__np=1; c.addEventListener("click",function(e){ e.stopPropagation(); }); } }); if(!tr.__hov){ tr.__hov=1; tr.addEventListener("mouseenter",function(){ tr.style.background="rgba(255,255,255,.03)"; }); tr.addEventListener("mouseleave",function(){ tr.style.background=""; }); } }); }catch(e){} if(obs)obs.observe(list,{childList:true,subtree:true}); } obs=new MutationObserver(function(){ clean(); }); obs.observe(list,{childList:true,subtree:true}); setTimeout(clean,60); setTimeout(clean,400); })(); /* ===== HOTPATCH Sprint 14: chat + summary ===== */ /* ===== Sprint 14: переписка по отклику + ИИ-саммари ===== */ function applyChatFields(id){ var dlg=document.getElementById("dialog"); if(!dlg)return; var grid=dlg.querySelector("#editscope .grid")||dlg.querySelector(".grid"); if(!grid)return; if(grid.querySelector('[data-xk="chat"]'))return; var rec=(typeof recOf==="function")?recOf("applies",id):null; if(!rec)return; function save1(k,v){ rec[k]=v; try{ if(typeof save==="function")save(); }catch(e){} } var f1=document.createElement("div"); f1.className="f fw"; var l1=document.createElement("div"); l1.className="lab"; l1.textContent="Переписка / о чём общались"; f1.appendChild(l1); var ta=document.createElement("textarea"); ta.setAttribute("data-xk","chat"); ta.style.cssText="width:100%;min-height:140px;box-sizing:border-box"; ta.placeholder="Вставь переписку с работодателем или заметку о разговоре"; ta.value=rec.chat||""; ta.addEventListener("input",function(){ save1("chat",ta.value); }); f1.appendChild(ta); grid.appendChild(f1); var bar=document.createElement("div"); bar.className="f fw"; bar.style.cssText="flex-direction:row;align-items:center;gap:10px"; var btn=document.createElement("button"); btn.className="btn"; btn.textContent="Свернуть в саммари (ИИ)"; var stt=document.createElement("span"); stt.className="muted small"; btn.onclick=function(){ var chat=ta.value.trim(); if(!chat){ stt.textContent="Вставь переписку выше"; return; } if(!window.__key){ stt.textContent="ИИ-ключ не настроен (окно «AI · Из переписки»)"; return; } btn.disabled=true; stt.textContent="Свожу… (5–15 сек)"; apiPost({action:"ai_parse",key:window.__key,dialog:"Переписка по отклику на вакансию. Кратко: о чём договорились, что просили, следующий шаг.\n\n"+chat}).then(function(r){ btn.disabled=false; if(r&&r.ok&&r.fields){ var f=r.fields, parts=[]; if(f.want)parts.push("Запрос: "+f.want); if(f.agreed)parts.push("Договорились: "+f.agreed); if(f.pain)parts.push("Важное: "+f.pain); if(f.next)parts.push("Следующий шаг: "+f.next); if(f.concl)parts.push(f.concl); var sum=parts.join("\n")||(f.concl||""); var out=grid.querySelector('[data-xk="chatsum"]'); if(out){ out.value=sum; save1("chatsum",sum); } stt.textContent="Готово. Проверь саммари ниже."; } else if(r&&r.error==="nokey"){ stt.textContent="ИИ-ключ не настроен."; } else if(r&&r.error==="auth"){ stt.textContent="Сессия истекла, войди заново."; } else { stt.textContent="Не разобралось, поправь текст."; } }).catch(function(){ btn.disabled=false; stt.textContent="Нет связи с сервером"; }); }; bar.appendChild(btn); bar.appendChild(stt); grid.appendChild(bar); var f2=document.createElement("div"); f2.className="f fw"; var l2=document.createElement("div"); l2.className="lab"; l2.textContent="Саммари (о чём договорились)"; f2.appendChild(l2); var ta2=document.createElement("textarea"); ta2.setAttribute("data-xk","chatsum"); ta2.style.cssText="width:100%;min-height:90px;box-sizing:border-box"; ta2.value=rec.chatsum||""; ta2.addEventListener("input",function(){ save1("chatsum",ta2.value); }); f2.appendChild(ta2); grid.appendChild(f2); } (function(){ if(typeof openEdit!=="function")return; if(openEdit.__chatfields)return; var _oe=openEdit; openEdit=function(coll,id){ _oe(coll,id); if(coll==="applies"){ setTimeout(function(){ try{ applyChatFields(id); }catch(e){} },240); } }; openEdit.__chatfields=1; })(); /* ===== HOTPATCH Sprint 15: tg-chat + date groups + field priority ===== */ /* ===== Sprint 15: телеграм-переписка + группировка откликов по датам + приоритет полей ===== */ /* дата в человеческий вид */ function crmFmtDate(d){ if(!d)return "Без даты"; var t=Date.parse(d); if(isNaN(t))return d; var dd=new Date(t), now=new Date(); var today=new Date(now.getFullYear(),now.getMonth(),now.getDate()); var that=new Date(dd.getFullYear(),dd.getMonth(),dd.getDate()); var diff=Math.round((today-that)/86400000); if(diff===0)return "Сегодня"; if(diff===1)return "Вчера"; var m=["января","февраля","марта","апреля","мая","июня","июля","августа","сентября","октября","ноября","декабря"]; return dd.getDate()+" "+m[dd.getMonth()]+" "+dd.getFullYear(); } /* А. Переписка пузырями */ function crmChatBubbles(id){ var dlg=document.getElementById("dialog"); if(!dlg)return; var grid=dlg.querySelector("#editscope .grid")||dlg.querySelector(".grid"); if(!grid)return; var ta=grid.querySelector('[data-xk="chat"]'); if(!ta)return; var wrap=ta.closest(".f"); if(!wrap)return; if(wrap.querySelector(".tgchat"))return; var rec=(typeof recOf==="function")?recOf("applies",id):null; if(!rec)return; var box=document.createElement("div"); box.className="tgchat"; box.style.cssText="display:flex;flex-direction:column;gap:6px;padding:10px;border:1px solid var(--line,#2a2f37);border-radius:10px;background:rgba(0,0,0,.15);max-height:360px;overflow:auto"; function build(){ box.innerHTML=""; var t=(rec.chat||""); if(!t.trim()){ box.innerHTML='
Переписки пока нет
'; return; } var parts=t.split(/\n\n(?=(?:Я|Работодатель):\s)/); parts.forEach(function(p){ var me=/^Я:\s/.test(p); var body=p.replace(/^(Я|Работодатель):\s*/,""); var b=document.createElement("div"); b.style.cssText="max-width:80%;padding:7px 11px;border-radius:12px;font-size:13px;line-height:1.4;white-space:pre-wrap;word-break:break-word;"+(me?"align-self:flex-end;background:#2b5278;color:#fff;border-bottom-right-radius:4px":"align-self:flex-start;background:var(--card,#232830);color:var(--fg,#cbd3dc);border:1px solid var(--line,#2a2f37);border-bottom-left-radius:4px"); b.textContent=body; box.appendChild(b); }); } build(); ta.style.display="none"; var toggle=document.createElement("button"); toggle.type="button"; toggle.className="btn sm"; toggle.textContent="Править текстом"; toggle.style.marginTop="6px"; var editing=false; toggle.onclick=function(){ editing=!editing; if(editing){ ta.style.display=""; box.style.display="none"; toggle.textContent="Показать чат"; } else { ta.style.display="none"; box.style.display=""; build(); toggle.textContent="Править текстом"; } }; ta.parentNode.insertBefore(box, ta); ta.parentNode.appendChild(toggle); if(!ta.__syncb){ ta.__syncb=1; ta.addEventListener("input",function(){ rec.chat=ta.value; }); } } /* В. Приоритет полей: важное вверх */ function crmApplyPriority(){ var dlg=document.getElementById("dialog"); if(!dlg)return; var grid=dlg.querySelector("#editscope .grid")||dlg.querySelector(".grid"); if(!grid)return; var pr="data-k:company,data-k:vacancy,data-k:status,data-k:next,data-k:nextdate,data-xk:resume,data-xk:cover,data-xk:chat".split(",").map(function(x){return x.split(":");}); for(var i=pr.length-1;i>=0;i--){ var el=grid.querySelector("["+pr[i][0]+'="'+pr[i][1]+'"]'); var f=el&&el.closest(".f"); if(f && f.parentNode===grid){ grid.insertBefore(f, grid.firstChild); } } } var _openEdit_o=openEdit; openEdit=function(coll,id){ _openEdit_o(coll,id); if(coll==="applies"){ setTimeout(function(){ try{ crmChatBubbles(id); crmApplyPriority(); }catch(e){} },320); } }; /* Б. Группировка списка откликов по датам (поллер) */ (function(){ var lastSig=""; setInterval(function(){ try{ if((window.TAB||"")!=="applies"){ lastSig=""; return; } var list=document.getElementById("list"); if(!list)return; var tbody=list.querySelector("tbody"); if(!tbody)return; var rows=[].slice.call(tbody.querySelectorAll("tr")).filter(function(tr){return !tr.classList.contains("dategrp");}); if(!rows.length)return; var sig=rows.map(function(tr){var m=(tr.getAttribute("onclick")||"").match(/'applies','([^']+)'/);return m?m[1]:"";}).join(","); if(sig===lastSig && tbody.querySelector(".dategrp"))return; lastSig=sig; [].slice.call(tbody.querySelectorAll("tr.dategrp")).forEach(function(x){x.parentNode.removeChild(x);}); var last=null; rows.forEach(function(tr){ var m=(tr.getAttribute("onclick")||"").match(/'applies','([^']+)'/); if(!m)return; var rec=(typeof recOf==="function")?recOf("applies",m[1]):null; if(!rec)return; var d=rec.date||""; if(d!==last){ last=d; var hr=document.createElement("tr");hr.className="dategrp";var td=document.createElement("td");td.colSpan=8;td.style.cssText="padding:12px 7px 4px;font-size:12px;text-transform:uppercase;letter-spacing:.04em;opacity:.5;font-weight:600";td.textContent=crmFmtDate(d);hr.appendChild(td);tbody.insertBefore(hr,tr);} }); }catch(e){} },500); })(); /* ===== HOTPATCH Sprint 16: card as page ===== */ /* ===== Sprint 16: карточка как страница (не попап) ===== */ (function(){ if(document.getElementById("crmpagecss"))return; var st=document.createElement("style");st.id="crmpagecss"; st.textContent= "#modal.pagemode{background:var(--bg,#0d1117)!important;padding:0!important;align-items:flex-start!important;overflow:auto!important}"+ "#modal.pagemode .dialog,#modal.pagemode .dialog.cardx{max-width:920px!important;width:100%!important;margin:0 auto!important;background:transparent!important;border:none!important;box-shadow:none!important;min-height:auto!important;border-radius:0!important;padding:16px 20px 70px!important}"+ "#modal.pagemode .cardhead{background:var(--bg,#0d1117)!important;border-bottom:1px solid var(--line,#2a2f37)!important}"+ "#crmback{display:inline-flex;align-items:center;gap:6px;background:transparent;border:none;color:var(--fg,#8ea0b5);cursor:pointer;font-size:14px;padding:6px 0;margin-bottom:6px;opacity:.8}"+ "#crmback:hover{opacity:1}"; document.head.appendChild(st); })(); function crmPageBack(){ var dlg=document.getElementById("dialog"); var close=null; if(dlg){ [].slice.call(dlg.querySelectorAll("button")).forEach(function(b){ if(/^\s*Закрыть\s*$/.test((b.textContent||"").trim()))close=b; }); } if(close){ close.click(); } else { var m=document.getElementById("modal"); if(m){ m.style.display="none"; } } } function crmMakePage(){ var m=document.getElementById("modal"); if(!m)return; m.classList.add("pagemode"); var dlg=document.getElementById("dialog"); if(!dlg)return; if(dlg.querySelector("#crmback"))return; var back=document.createElement("button"); back.type="button"; back.id="crmback"; back.innerHTML="← Назад к списку"; back.onclick=crmPageBack; dlg.insertBefore(back, dlg.firstChild); } var _openEdit_q=openEdit; openEdit=function(coll,id){ _openEdit_q(coll,id); setTimeout(function(){ try{ crmMakePage(); }catch(e){} },90); setTimeout(function(){ try{ crmMakePage(); }catch(e){} },360); }; /* ===== HOTPATCH Sprint 17: read ticks + reply button ===== */ /* ===== Sprint 17: галочки прочтения + кнопка «Ответить на hh» ===== */ /* переопределяем рендер переписки: из chatj (со статусом прочтения), с галочками */ function crmChatBubbles(id){ var dlg=document.getElementById("dialog"); if(!dlg)return; var grid=dlg.querySelector("#editscope .grid")||dlg.querySelector(".grid"); if(!grid)return; var ta=grid.querySelector('[data-xk="chat"]'); if(!ta)return; var wrap=ta.closest(".f"); if(!wrap)return; if(wrap.querySelector(".tgchat"))return; var rec=(typeof recOf==="function")?recOf("applies",id):null; if(!rec)return; var msgs=[]; if(rec.chatj){ try{ msgs=JSON.parse(rec.chatj); }catch(e){} } if(!msgs.length){ var t=(rec.chat||""); if(t.trim()){ t.split(/\n\n(?=(?:Я|Работодатель):\s)/).forEach(function(p){ var me=/^Я:\s/.test(p); msgs.push({me:me, t:p.replace(/^(Я|Работодатель):\s*/,""), r:undefined}); }); } } var box=document.createElement("div"); box.className="tgchat"; box.style.cssText="display:flex;flex-direction:column;gap:6px;padding:10px;border:1px solid var(--line,#2a2f37);border-radius:10px;background:rgba(0,0,0,.15);max-height:420px;overflow:auto"; if(!msgs.length){ box.innerHTML='
Переписки пока нет
'; } else msgs.forEach(function(m){ var me=m.me; var b=document.createElement("div"); b.style.cssText="max-width:80%;padding:7px 11px;border-radius:12px;font-size:13px;line-height:1.4;white-space:pre-wrap;word-break:break-word;"+(me?"align-self:flex-end;background:#2b5278;color:#fff;border-bottom-right-radius:4px":"align-self:flex-start;background:var(--card,#232830);color:var(--fg,#cbd3dc);border:1px solid var(--line,#2a2f37);border-bottom-left-radius:4px"); b.textContent=m.t; if(me && m.r!==undefined){ var tk=document.createElement("span"); tk.textContent=m.r?" ✓✓":" ✓"; tk.title=m.r?"Прочитано работодателем":"Доставлено, не прочитано"; tk.style.cssText="font-size:11px;opacity:.95;color:"+(m.r?"#8fe0ff":"#cfe0f0"); b.appendChild(tk); } box.appendChild(b); }); ta.style.display="none"; var toggle=document.createElement("button"); toggle.type="button"; toggle.className="btn sm"; toggle.textContent="Править текстом"; toggle.style.marginTop="6px"; var editing=false; toggle.onclick=function(){ editing=!editing; if(editing){ ta.style.display=""; box.style.display="none"; toggle.textContent="Показать чат"; } else { ta.style.display="none"; box.style.display=""; toggle.textContent="Править текстом"; } }; ta.parentNode.insertBefore(box, ta); ta.parentNode.appendChild(toggle); } /* кнопка «Ответить на hh» — открывает нужный чат */ function crmReplyBtn(id){ var dlg=document.getElementById("dialog"); if(!dlg)return; var rec=(typeof recOf==="function")?recOf("applies",id):null; if(!rec||!rec.chatid)return; if(dlg.querySelector("#crmreply"))return; var openVac=null; [].slice.call(dlg.querySelectorAll("a,button")).forEach(function(b){ if(/Открыть вакансию/.test(b.textContent||""))openVac=b; }); var a=document.createElement("a"); a.id="crmreply"; a.className="btn"; a.href="https://hh.ru/chat/"+rec.chatid; a.target="_blank"; a.rel="noopener"; a.textContent="Ответить на hh ↗"; if(openVac && openVac.parentNode){ openVac.parentNode.insertBefore(a, openVac.nextSibling); } else { var bar=dlg.querySelector(".actbar")||dlg.querySelector(".rowbtns"); if(bar)bar.appendChild(a); } } var _openEdit_r=openEdit; openEdit=function(coll,id){ _openEdit_r(coll,id); if(coll==="applies"){ setTimeout(function(){ try{ crmReplyBtn(id); }catch(e){} },380); } }; /* ===== HOTPATCH Sprint 12: version badge ===== */ (function(){ var V="v19 · 04.09.2026"; window.__crmVer=V; function inject(){ var brand=document.querySelector("header.top .brand"); if(!brand)return false; if(document.getElementById("crmver"))return true; var s=document.createElement("span"); s.id="crmver"; s.textContent=V; s.title="Версия загруженного приложения. Нет метки или дата старее — кэш, жми Cmd+Shift+R."; s.style.cssText="display:inline-block;padding:2px 8px;border:1px solid var(--line,#2a2f37);border-radius:20px;font-size:11px;opacity:.55;vertical-align:middle;white-space:nowrap"; var sub=brand.querySelector("small"); if(sub){ sub.appendChild(document.createTextNode("\u2003\u00b7\u2003")); sub.appendChild(s); } else { brand.appendChild(s); } return true; } if(!inject()){ var k=0; var t=setInterval(function(){ if(inject()||++k>25)clearInterval(t); },200); } })();