feat: кнопка «Направить в работу сотруднику»
Один клик — копирует сообщение в буфер, сохраняет задачу в историю со статусом in_progress, показывает toast. Основная primary-кнопка. Старые «Скопировать» / «Сохранить» оставлены как secondary действия. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0455123d8e
commit
d5c24f39bc
28
index.html
28
index.html
@ -375,6 +375,7 @@ const I18N = {
|
|||||||
"task.score":"вес","task.assignee":"Ответственный","task.fallback":"Резерв",
|
"task.score":"вес","task.assignee":"Ответственный","task.fallback":"Резерв",
|
||||||
"task.copy":"Скопировать сообщение","task.copied":"Скопировано",
|
"task.copy":"Скопировать сообщение","task.copied":"Скопировано",
|
||||||
"task.save":"Сохранить в историю","task.saved":"Сохранено",
|
"task.save":"Сохранить в историю","task.saved":"Сохранено",
|
||||||
|
"task.send":"➤ Направить в работу","task.sent":"Направлено сотруднику",
|
||||||
"task.none":"Нет доступного ответственного. Все в зоне в отпуске.",
|
"task.none":"Нет доступного ответственного. Все в зоне в отпуске.",
|
||||||
"task.assign_manual":"Назначить вручную","task.take_self":"Принять на себя",
|
"task.assign_manual":"Назначить вручную","task.take_self":"Принять на себя",
|
||||||
"task.delegate_note":"Замещает {primary} (отпуск до {until})",
|
"task.delegate_note":"Замещает {primary} (отпуск до {until})",
|
||||||
@ -399,6 +400,7 @@ const I18N = {
|
|||||||
"task.score":"score","task.assignee":"Assignee","task.fallback":"Backup",
|
"task.score":"score","task.assignee":"Assignee","task.fallback":"Backup",
|
||||||
"task.copy":"Copy message","task.copied":"Copied",
|
"task.copy":"Copy message","task.copied":"Copied",
|
||||||
"task.save":"Save to history","task.saved":"Saved",
|
"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.none":"No available assignee. All in zone are on vacation.",
|
||||||
"task.assign_manual":"Assign manually","task.take_self":"Take it myself",
|
"task.assign_manual":"Assign manually","task.take_self":"Take it myself",
|
||||||
"task.delegate_note":"Substituting for {primary} (vacation until {until})",
|
"task.delegate_note":"Substituting for {primary} (vacation until {until})",
|
||||||
@ -564,10 +566,12 @@ function renderAssignee(root, zoneId, result){
|
|||||||
${note}
|
${note}
|
||||||
<pre>${esc(msg)}</pre>
|
<pre>${esc(msg)}</pre>
|
||||||
<div class="actions">
|
<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>
|
<button class="ghost" id="save-history">${t("task.save")}</button>
|
||||||
</div>`;
|
</div>`;
|
||||||
root.appendChild(card);
|
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("#copy-msg").onclick = () => copyToClipboard(msg, t("task.copied"));
|
||||||
card.querySelector("#save-history").onclick = () => saveTaskToHistory(result, zone, main, role);
|
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>
|
<div class="meta">${t("task.assignee")} · ${esc(zoneTitle(zone))} · ${esc(main.contact||"—")}</div>
|
||||||
<pre>${esc(msg)}</pre>
|
<pre>${esc(msg)}</pre>
|
||||||
<div class="actions">
|
<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>
|
<button class="ghost" id="save-history">${t("task.save")}</button>
|
||||||
</div>`;
|
</div>`;
|
||||||
root.appendChild(card);
|
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("#copy-msg").onclick = () => copyToClipboard(msg, t("task.copied"));
|
||||||
card.querySelector("#save-history").onclick = () => saveTaskToHistory(result, zone, main, "primary", true);
|
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);
|
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 = {
|
const entry = {
|
||||||
id:"h-"+Date.now(),
|
id:"h-"+Date.now(),
|
||||||
createdAt:new Date().toISOString(),
|
createdAt:new Date().toISOString(),
|
||||||
@ -628,11 +634,19 @@ function saveTaskToHistory(result, zone, main, role, manualOverride=false){
|
|||||||
delegateUsed: role==="delegate",
|
delegateUsed: role==="delegate",
|
||||||
manualOverride,
|
manualOverride,
|
||||||
comments: [],
|
comments: [],
|
||||||
status: "open"
|
status
|
||||||
};
|
};
|
||||||
state.history.entries.unshift(entry);
|
state.history.entries.unshift(entry);
|
||||||
state.history.entries = state.history.entries.slice(0,100);
|
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(){
|
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 => ({"&":"&","<":"<",">":">",'"':""","'":"'"}[c]));
|
const esc = s => (s??"").toString().replace(/[&<>"']/g, c => ({"&":"&","<":"<",">":">",'"':""","'":"'"}[c]));
|
||||||
function copyToClipboard(text, toastMsg){
|
function copyToClipboard(text, toastMsg){
|
||||||
if (navigator.clipboard && navigator.clipboard.writeText){
|
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);
|
} else fallbackCopy(text, toastMsg);
|
||||||
}
|
}
|
||||||
function fallbackCopy(text, toastMsg){
|
function fallbackCopy(text, toastMsg){
|
||||||
const ta = document.createElement("textarea"); ta.value = text; ta.style.position="fixed"; ta.style.left="-9999px";
|
const ta = document.createElement("textarea"); ta.value = text; ta.style.position="fixed"; ta.style.left="-9999px";
|
||||||
document.body.appendChild(ta); ta.select();
|
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);
|
document.body.removeChild(ta);
|
||||||
}
|
}
|
||||||
function toast(msg){
|
function toast(msg){
|
||||||
|
|||||||
28
mvp.html
28
mvp.html
@ -172,6 +172,7 @@ const I18N = {
|
|||||||
"task.score":"вес", "task.assignee":"Ответственный", "task.fallback":"Резерв",
|
"task.score":"вес", "task.assignee":"Ответственный", "task.fallback":"Резерв",
|
||||||
"task.copy":"Скопировать сообщение", "task.copied":"Скопировано",
|
"task.copy":"Скопировать сообщение", "task.copied":"Скопировано",
|
||||||
"task.save":"Сохранить в историю", "task.saved":"Сохранено",
|
"task.save":"Сохранить в историю", "task.saved":"Сохранено",
|
||||||
|
"task.send":"➤ Направить в работу", "task.sent":"Направлено сотруднику",
|
||||||
"task.none":"Нет доступного ответственного. Все в зоне в отпуске.",
|
"task.none":"Нет доступного ответственного. Все в зоне в отпуске.",
|
||||||
"task.assign_manual":"Назначить вручную", "task.take_self":"Принять на себя",
|
"task.assign_manual":"Назначить вручную", "task.take_self":"Принять на себя",
|
||||||
"task.delegate_note":"Замещает {primary} (отпуск до {until})",
|
"task.delegate_note":"Замещает {primary} (отпуск до {until})",
|
||||||
@ -196,6 +197,7 @@ const I18N = {
|
|||||||
"task.score":"score", "task.assignee":"Assignee", "task.fallback":"Backup",
|
"task.score":"score", "task.assignee":"Assignee", "task.fallback":"Backup",
|
||||||
"task.copy":"Copy message", "task.copied":"Copied",
|
"task.copy":"Copy message", "task.copied":"Copied",
|
||||||
"task.save":"Save to history", "task.saved":"Saved",
|
"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.none":"No available assignee. All in zone are on vacation.",
|
||||||
"task.assign_manual":"Assign manually", "task.take_self":"Take it myself",
|
"task.assign_manual":"Assign manually", "task.take_self":"Take it myself",
|
||||||
"task.delegate_note":"Substituting for {primary} (vacation until {until})",
|
"task.delegate_note":"Substituting for {primary} (vacation until {until})",
|
||||||
@ -373,10 +375,12 @@ function renderAssignee(root, zoneId, result){
|
|||||||
${note}
|
${note}
|
||||||
<pre>${esc(msg)}</pre>
|
<pre>${esc(msg)}</pre>
|
||||||
<div class="actions">
|
<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>
|
<button id="save-history" class="ghost">${t("task.save")}</button>
|
||||||
</div>`;
|
</div>`;
|
||||||
root.appendChild(card);
|
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("#copy-msg").onclick = () => copyToClipboard(msg, t("task.copied"));
|
||||||
card.querySelector("#save-history").onclick = () => saveTaskToHistory(result, zone, main, role);
|
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>
|
<div class="meta">${t("task.assignee")} · ${esc(zoneTitle(zone))} · ${esc(main.contact||"—")}</div>
|
||||||
<pre>${esc(msg)}</pre>
|
<pre>${esc(msg)}</pre>
|
||||||
<div class="actions">
|
<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>
|
<button id="save-history" class="ghost">${t("task.save")}</button>
|
||||||
</div>`;
|
</div>`;
|
||||||
root.appendChild(card);
|
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("#copy-msg").onclick = () => copyToClipboard(msg, t("task.copied"));
|
||||||
card.querySelector("#save-history").onclick = () => saveTaskToHistory(result, zone, main, "primary", true);
|
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);
|
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 = {
|
const entry = {
|
||||||
id:"h-"+Date.now(),
|
id:"h-"+Date.now(),
|
||||||
createdAt:new Date().toISOString(),
|
createdAt:new Date().toISOString(),
|
||||||
@ -437,11 +443,19 @@ function saveTaskToHistory(result, zone, main, role, manualOverride=false){
|
|||||||
delegateUsed: role==="delegate",
|
delegateUsed: role==="delegate",
|
||||||
manualOverride,
|
manualOverride,
|
||||||
comments: [],
|
comments: [],
|
||||||
status: "open"
|
status
|
||||||
};
|
};
|
||||||
state.history.entries.unshift(entry);
|
state.history.entries.unshift(entry);
|
||||||
state.history.entries = state.history.entries.slice(0,100);
|
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 =====
|
// ===== 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 => ({"&":"&","<":"<",">":">",'"':""","'":"'"}[c])); }
|
function esc(s){ return (s??"").toString().replace(/[&<>"']/g, c => ({"&":"&","<":"<",">":">",'"':""","'":"'"}[c])); }
|
||||||
function copyToClipboard(text, toastMsg){
|
function copyToClipboard(text, toastMsg){
|
||||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
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 {
|
} else {
|
||||||
fallbackCopy(text, toastMsg);
|
fallbackCopy(text, toastMsg);
|
||||||
}
|
}
|
||||||
@ -601,7 +615,7 @@ function copyToClipboard(text, toastMsg){
|
|||||||
function fallbackCopy(text, toastMsg){
|
function fallbackCopy(text, toastMsg){
|
||||||
const ta = document.createElement("textarea"); ta.value = text; ta.style.position = "fixed"; ta.style.left="-9999px";
|
const ta = document.createElement("textarea"); ta.value = text; ta.style.position = "fixed"; ta.style.left="-9999px";
|
||||||
document.body.appendChild(ta); ta.select();
|
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);
|
document.body.removeChild(ta);
|
||||||
}
|
}
|
||||||
function toast(msg){
|
function toast(msg){
|
||||||
|
|||||||
Reference in New Issue
Block a user