feat: кнопка «Направить в работу сотруднику»

Один клик — копирует сообщение в буфер, сохраняет задачу в историю
со статусом in_progress, показывает toast. Основная primary-кнопка.
Старые «Скопировать» / «Сохранить» оставлены как secondary действия.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
zhanakova 2026-05-22 13:47:51 +05:00
parent 0455123d8e
commit d5c24f39bc
2 changed files with 42 additions and 14 deletions

View File

@ -375,6 +375,7 @@ const I18N = {
"task.score":"вес","task.assignee":"Ответственный","task.fallback":"Резерв",
"task.copy":"Скопировать сообщение","task.copied":"Скопировано",
"task.save":"Сохранить в историю","task.saved":"Сохранено",
"task.send":"➤ Направить в работу","task.sent":"Направлено сотруднику",
"task.none":"Нет доступного ответственного. Все в зоне в отпуске.",
"task.assign_manual":"Назначить вручную","task.take_self":"Принять на себя",
"task.delegate_note":"Замещает {primary} (отпуск до {until})",
@ -399,6 +400,7 @@ const I18N = {
"task.score":"score","task.assignee":"Assignee","task.fallback":"Backup",
"task.copy":"Copy message","task.copied":"Copied",
"task.save":"Save to history","task.saved":"Saved",
"task.send":"➤ Send to assignee","task.sent":"Sent to assignee",
"task.none":"No available assignee. All in zone are on vacation.",
"task.assign_manual":"Assign manually","task.take_self":"Take it myself",
"task.delegate_note":"Substituting for {primary} (vacation until {until})",
@ -564,10 +566,12 @@ function renderAssignee(root, zoneId, result){
${note}
<pre>${esc(msg)}</pre>
<div class="actions">
<button class="btn" id="copy-msg">${t("task.copy")}</button>
<button class="btn" id="send-work">${t("task.send")}</button>
<button class="ghost" id="copy-msg">${t("task.copy")}</button>
<button class="ghost" id="save-history">${t("task.save")}</button>
</div>`;
root.appendChild(card);
card.querySelector("#send-work").onclick = () => sendToWork(result, zone, main, role, msg);
card.querySelector("#copy-msg").onclick = () => copyToClipboard(msg, t("task.copied"));
card.querySelector("#save-history").onclick = () => saveTaskToHistory(result, zone, main, role);
}
@ -598,10 +602,12 @@ function manualAssign(result, zone){
<div class="meta">${t("task.assignee")} · ${esc(zoneTitle(zone))} · ${esc(main.contact||"—")}</div>
<pre>${esc(msg)}</pre>
<div class="actions">
<button class="btn" id="copy-msg">${t("task.copy")}</button>
<button class="btn" id="send-work">${t("task.send")}</button>
<button class="ghost" id="copy-msg">${t("task.copy")}</button>
<button class="ghost" id="save-history">${t("task.save")}</button>
</div>`;
root.appendChild(card);
card.querySelector("#send-work").onclick = () => sendToWork(result, zone, main, "primary", msg, true);
card.querySelector("#copy-msg").onclick = () => copyToClipboard(msg, t("task.copied"));
card.querySelector("#save-history").onclick = () => saveTaskToHistory(result, zone, main, "primary", true);
}
@ -617,7 +623,7 @@ function takeSelf(result, zone){
card.querySelector("#save-history").onclick = () => saveTaskToHistory(result, zone, self, "primary", true);
}
function saveTaskToHistory(result, zone, main, role, manualOverride=false){
function saveTaskToHistory(result, zone, main, role, manualOverride=false, status="open", silent=false){
const entry = {
id:"h-"+Date.now(),
createdAt:new Date().toISOString(),
@ -628,11 +634,19 @@ function saveTaskToHistory(result, zone, main, role, manualOverride=false){
delegateUsed: role==="delegate",
manualOverride,
comments: [],
status: "open"
status
};
state.history.entries.unshift(entry);
state.history.entries = state.history.entries.slice(0,100);
saveHist(); renderHistory(); toast(t("task.saved"));
saveHist(); renderHistory();
if (!silent) toast(t("task.saved"));
return entry;
}
function sendToWork(result, zone, main, role, msg, manualOverride=false){
copyToClipboard(msg, "");
saveTaskToHistory(result, zone, main, role, manualOverride, "in_progress", true);
toast(t("task.sent"));
}
function renderPeople(){
@ -780,13 +794,13 @@ const zoneTitle = z => state.lang==="en" && z.titleEn ? z.titleEn : z.title;
const esc = s => (s??"").toString().replace(/[&<>"']/g, c => ({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"}[c]));
function copyToClipboard(text, toastMsg){
if (navigator.clipboard && navigator.clipboard.writeText){
navigator.clipboard.writeText(text).then(()=>toast(toastMsg)).catch(()=>fallbackCopy(text, toastMsg));
navigator.clipboard.writeText(text).then(()=>{ if(toastMsg) toast(toastMsg); }).catch(()=>fallbackCopy(text, toastMsg));
} else fallbackCopy(text, toastMsg);
}
function fallbackCopy(text, toastMsg){
const ta = document.createElement("textarea"); ta.value = text; ta.style.position="fixed"; ta.style.left="-9999px";
document.body.appendChild(ta); ta.select();
try { document.execCommand("copy"); toast(toastMsg); } catch(e){}
try { document.execCommand("copy"); if(toastMsg) toast(toastMsg); } catch(e){}
document.body.removeChild(ta);
}
function toast(msg){

View File

@ -172,6 +172,7 @@ const I18N = {
"task.score":"вес", "task.assignee":"Ответственный", "task.fallback":"Резерв",
"task.copy":"Скопировать сообщение", "task.copied":"Скопировано",
"task.save":"Сохранить в историю", "task.saved":"Сохранено",
"task.send":"➤ Направить в работу", "task.sent":"Направлено сотруднику",
"task.none":"Нет доступного ответственного. Все в зоне в отпуске.",
"task.assign_manual":"Назначить вручную", "task.take_self":"Принять на себя",
"task.delegate_note":"Замещает {primary} (отпуск до {until})",
@ -196,6 +197,7 @@ const I18N = {
"task.score":"score", "task.assignee":"Assignee", "task.fallback":"Backup",
"task.copy":"Copy message", "task.copied":"Copied",
"task.save":"Save to history", "task.saved":"Saved",
"task.send":"➤ Send to assignee", "task.sent":"Sent to assignee",
"task.none":"No available assignee. All in zone are on vacation.",
"task.assign_manual":"Assign manually", "task.take_self":"Take it myself",
"task.delegate_note":"Substituting for {primary} (vacation until {until})",
@ -373,10 +375,12 @@ function renderAssignee(root, zoneId, result){
${note}
<pre>${esc(msg)}</pre>
<div class="actions">
<button id="copy-msg">${t("task.copy")}</button>
<button id="send-work">${t("task.send")}</button>
<button id="copy-msg" class="ghost">${t("task.copy")}</button>
<button id="save-history" class="ghost">${t("task.save")}</button>
</div>`;
root.appendChild(card);
card.querySelector("#send-work").onclick = () => sendToWork(result, zone, main, role, msg);
card.querySelector("#copy-msg").onclick = () => copyToClipboard(msg, t("task.copied"));
card.querySelector("#save-history").onclick = () => saveTaskToHistory(result, zone, main, role);
}
@ -407,10 +411,12 @@ function manualAssign(result, zone){
<div class="meta">${t("task.assignee")} · ${esc(zoneTitle(zone))} · ${esc(main.contact||"—")}</div>
<pre>${esc(msg)}</pre>
<div class="actions">
<button id="copy-msg">${t("task.copy")}</button>
<button id="send-work">${t("task.send")}</button>
<button id="copy-msg" class="ghost">${t("task.copy")}</button>
<button id="save-history" class="ghost">${t("task.save")}</button>
</div>`;
root.appendChild(card);
card.querySelector("#send-work").onclick = () => sendToWork(result, zone, main, "primary", msg, true);
card.querySelector("#copy-msg").onclick = () => copyToClipboard(msg, t("task.copied"));
card.querySelector("#save-history").onclick = () => saveTaskToHistory(result, zone, main, "primary", true);
}
@ -426,7 +432,7 @@ function takeSelf(result, zone){
card.querySelector("#save-history").onclick = () => saveTaskToHistory(result, zone, self, "primary", true);
}
function saveTaskToHistory(result, zone, main, role, manualOverride=false){
function saveTaskToHistory(result, zone, main, role, manualOverride=false, status="open", silent=false){
const entry = {
id:"h-"+Date.now(),
createdAt:new Date().toISOString(),
@ -437,11 +443,19 @@ function saveTaskToHistory(result, zone, main, role, manualOverride=false){
delegateUsed: role==="delegate",
manualOverride,
comments: [],
status: "open"
status
};
state.history.entries.unshift(entry);
state.history.entries = state.history.entries.slice(0,100);
saveHist(); renderHistory(); toast(t("task.saved"));
saveHist(); renderHistory();
if (!silent) toast(t("task.saved"));
return entry;
}
function sendToWork(result, zone, main, role, msg, manualOverride=false){
copyToClipboard(msg, "");
saveTaskToHistory(result, zone, main, role, manualOverride, "in_progress", true);
toast(t("task.sent"));
}
// ===== People view =====
@ -593,7 +607,7 @@ function zoneTitle(z){ return state.lang==="en" && z.titleEn ? z.titleEn : z.tit
function esc(s){ return (s??"").toString().replace(/[&<>"']/g, c => ({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"}[c])); }
function copyToClipboard(text, toastMsg){
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(()=>toast(toastMsg)).catch(()=>fallbackCopy(text, toastMsg));
navigator.clipboard.writeText(text).then(()=>{ if(toastMsg) toast(toastMsg); }).catch(()=>fallbackCopy(text, toastMsg));
} else {
fallbackCopy(text, toastMsg);
}
@ -601,7 +615,7 @@ function copyToClipboard(text, toastMsg){
function fallbackCopy(text, toastMsg){
const ta = document.createElement("textarea"); ta.value = text; ta.style.position = "fixed"; ta.style.left="-9999px";
document.body.appendChild(ta); ta.select();
try { document.execCommand("copy"); toast(toastMsg); } catch(e){}
try { document.execCommand("copy"); if(toastMsg) toast(toastMsg); } catch(e){}
document.body.removeChild(ta);
}
function toast(msg){