diff --git a/voxtelesys_integration/public/js/hd_ticket.js b/voxtelesys_integration/public/js/hd_ticket.js new file mode 100644 index 0000000..c295a4f --- /dev/null +++ b/voxtelesys_integration/public/js/hd_ticket.js @@ -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( + ` ${__("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( + ` ${__("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( + '
" + ); + } + + 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(`${__("No calls or SMS yet")}`); + return; + } + + const rows = []; + calls.forEach((c) => { + const when = c.start_time ? frappe.datetime.prettyDate(c.start_time) : ""; + const icon = c.direction === "Outbound" ? "📤" : "📥"; + rows.push(` +