v2.1: settings SMS fields + HD Ticket/Contact/Lead form JS: hd_ticket.js
This commit is contained in:
parent
cdcb621a91
commit
b1a00c8bd9
1 changed files with 184 additions and 0 deletions
184
voxtelesys_integration/public/js/hd_ticket.js
Normal file
184
voxtelesys_integration/public/js/hd_ticket.js
Normal file
|
|
@ -0,0 +1,184 @@
|
||||||
|
/**
|
||||||
|
* HD Ticket form JS — adds Voxtelesys click-to-call and SMS controls,
|
||||||
|
* plus a history sidebar showing recent calls and SMS for this ticket's
|
||||||
|
* contact.
|
||||||
|
*
|
||||||
|
* Loaded via hooks.py:doctype_js. Requires frappe.boot.voxtelesys to be
|
||||||
|
* populated by the boot_session hook in api/voxtelesys.py.
|
||||||
|
*/
|
||||||
|
frappe.ui.form.on("HD Ticket", {
|
||||||
|
refresh(frm) {
|
||||||
|
if (!frappe.boot.voxtelesys || !frappe.boot.voxtelesys.enabled) return;
|
||||||
|
|
||||||
|
const phone = _resolveTicketPhone(frm);
|
||||||
|
|
||||||
|
// ---- Call button ----
|
||||||
|
if (phone) {
|
||||||
|
frm.add_custom_button(
|
||||||
|
`<i class="fa fa-phone"></i> ${__("Call {0}", [phone])}`,
|
||||||
|
() => {
|
||||||
|
frappe.call({
|
||||||
|
method: "voxtelesys_integration.api.voxtelesys.make_outbound_call",
|
||||||
|
args: {
|
||||||
|
to: phone,
|
||||||
|
reference_doctype: frm.doctype,
|
||||||
|
reference_name: frm.docname,
|
||||||
|
},
|
||||||
|
freeze: true,
|
||||||
|
freeze_message: __("Dialing {0}…", [phone]),
|
||||||
|
callback(r) {
|
||||||
|
if (r.message && r.message.ok) {
|
||||||
|
frappe.show_alert({
|
||||||
|
message: __("Calling {0}", [phone]),
|
||||||
|
indicator: "green",
|
||||||
|
});
|
||||||
|
frm.reload_doc();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
__("Voxtelesys")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- SMS button ----
|
||||||
|
if (phone && frappe.boot.voxtelesys.sms_enabled !== false) {
|
||||||
|
frm.add_custom_button(
|
||||||
|
`<i class="fa fa-comment"></i> ${__("Send SMS")}`,
|
||||||
|
() => _showSmsDialog(frm, phone),
|
||||||
|
__("Voxtelesys")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- History sidebar (calls + SMS) ----
|
||||||
|
_renderHistory(frm);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function _resolveTicketPhone(frm) {
|
||||||
|
// Prefer the linked contact's mobile, fall back to phone, fall back to a
|
||||||
|
// raw "phone" field if the site has one. We don't try to derive from
|
||||||
|
// raised_by (email) — that requires an extra lookup we can do later.
|
||||||
|
if (!frm.doc.contact) return null;
|
||||||
|
if (frm._vtx_contact_phone) return frm._vtx_contact_phone;
|
||||||
|
|
||||||
|
// Synchronously cached on the form via a one-shot lookup
|
||||||
|
frappe.db
|
||||||
|
.get_value("Contact", frm.doc.contact, ["mobile_no", "phone"])
|
||||||
|
.then((r) => {
|
||||||
|
const m = r.message || {};
|
||||||
|
const phone = m.mobile_no || m.phone || null;
|
||||||
|
if (phone) {
|
||||||
|
frm._vtx_contact_phone = phone;
|
||||||
|
frm.refresh();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _showSmsDialog(frm, defaultTo) {
|
||||||
|
const d = new frappe.ui.Dialog({
|
||||||
|
title: __("Send SMS"),
|
||||||
|
fields: [
|
||||||
|
{ fieldname: "to", fieldtype: "Data", label: __("To"), default: defaultTo, reqd: 1 },
|
||||||
|
{
|
||||||
|
fieldname: "body",
|
||||||
|
fieldtype: "Small Text",
|
||||||
|
label: __("Message"),
|
||||||
|
reqd: 1,
|
||||||
|
description: __("Max ~1600 chars; Voxtelesys auto-segments longer messages."),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
primary_action_label: __("Send"),
|
||||||
|
primary_action(values) {
|
||||||
|
frappe.call({
|
||||||
|
method: "voxtelesys_integration.api.sms.send_sms",
|
||||||
|
args: {
|
||||||
|
to: values.to,
|
||||||
|
body: values.body,
|
||||||
|
reference_doctype: frm.doctype,
|
||||||
|
reference_name: frm.docname,
|
||||||
|
},
|
||||||
|
freeze: true,
|
||||||
|
freeze_message: __("Sending SMS…"),
|
||||||
|
callback(r) {
|
||||||
|
if (r.message && r.message.ok) {
|
||||||
|
d.hide();
|
||||||
|
frappe.show_alert({
|
||||||
|
message: __("SMS sent"),
|
||||||
|
indicator: "green",
|
||||||
|
});
|
||||||
|
frm.reload_doc();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
d.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
function _renderHistory(frm) {
|
||||||
|
if (!frm.fields_dict.voxtelesys_history) {
|
||||||
|
// No HTML field exists — render in the sidebar instead
|
||||||
|
const $wrapper = frm.sidebar.sidebar.find(".vtx-history-wrapper");
|
||||||
|
if ($wrapper.length) return;
|
||||||
|
frm.sidebar.sidebar.append(
|
||||||
|
'<div class="form-sidebar-section vtx-history-wrapper">' +
|
||||||
|
'<div class="sidebar-label">' + __("Voxtelesys History") + "</div>" +
|
||||||
|
'<div class="vtx-history-content"><i>' + __("Loading…") + "</i></div>" +
|
||||||
|
"</div>"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Promise.all([
|
||||||
|
frappe.call({
|
||||||
|
method: "voxtelesys_integration.api.voxtelesys.get_linked_call_logs",
|
||||||
|
args: { doctype: frm.doctype, name: frm.docname },
|
||||||
|
}),
|
||||||
|
frappe.call({
|
||||||
|
method: "voxtelesys_integration.api.sms.get_linked_sms_logs",
|
||||||
|
args: { doctype: frm.doctype, name: frm.docname },
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
.then(([callsResp, smsResp]) => {
|
||||||
|
const calls = callsResp.message || [];
|
||||||
|
const sms = smsResp.message || [];
|
||||||
|
const $content = frm.sidebar.sidebar.find(".vtx-history-content");
|
||||||
|
if (!$content.length) return;
|
||||||
|
|
||||||
|
if (!calls.length && !sms.length) {
|
||||||
|
$content.html(`<i class="text-muted">${__("No calls or SMS yet")}</i>`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rows = [];
|
||||||
|
calls.forEach((c) => {
|
||||||
|
const when = c.start_time ? frappe.datetime.prettyDate(c.start_time) : "";
|
||||||
|
const icon = c.direction === "Outbound" ? "📤" : "📥";
|
||||||
|
rows.push(`
|
||||||
|
<div class="vtx-history-row">
|
||||||
|
<a href="/app/voxtelesys-call-log/${c.name}">
|
||||||
|
${icon} ${frappe.utils.escape_html(c.from_number || c.to_number || "")}
|
||||||
|
</a>
|
||||||
|
<span class="text-muted small">${frappe.utils.escape_html(c.status || "")} · ${when}</span>
|
||||||
|
</div>`);
|
||||||
|
});
|
||||||
|
sms.forEach((s) => {
|
||||||
|
const when = (s.received_at || s.sent_at) ? frappe.datetime.prettyDate(s.received_at || s.sent_at) : "";
|
||||||
|
const icon = s.direction === "Outbound" ? "💬↗" : "💬↙";
|
||||||
|
const preview = (s.body || "").slice(0, 40);
|
||||||
|
rows.push(`
|
||||||
|
<div class="vtx-history-row">
|
||||||
|
<a href="/app/voxtelesys-sms-log/${s.name}">
|
||||||
|
${icon} ${frappe.utils.escape_html(preview)}${(s.body || "").length > 40 ? "…" : ""}
|
||||||
|
</a>
|
||||||
|
<span class="text-muted small">${when}</span>
|
||||||
|
</div>`);
|
||||||
|
});
|
||||||
|
$content.html(rows.join(""));
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
const $content = frm.sidebar.sidebar.find(".vtx-history-content");
|
||||||
|
if ($content.length) $content.html(`<i class="text-muted">${__("Failed to load history")}</i>`);
|
||||||
|
});
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue