You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

504 lines
15 KiB

define(function(require) {
var $ = require('jquery'),
_ = require('lodash'),
monster = require('monster');
var app = {
requests: {},
appFlags: {
callLogs: {
devices: []
}
},
subscribe: {
'voip.callLogs.render': 'callLogsRender'
},
callLogsRender: function(args) {
var self = this;
self.callLogsGetData(function() {
self.callLogsRenderContent(args.parent, args.fromDate, args.toDate, args.type, args.callback);
});
},
callLogsGetData: function(globalCallback) {
var self = this;
monster.parallel({
devices: function(callback) {
self.callLogsListDevices(function(devices) {
callback && callback(null, devices);
});
}
}, function(err, results) {
self.appFlags.callLogs.devices = _.keyBy(results.devices, 'id');
globalCallback && globalCallback();
});
},
callLogsRenderContent: function(parent, fromDate, toDate, type, callback) {
var self = this,
template,
defaultDateRange = 1,
maxDateRange = 31;
if (!toDate && !fromDate) {
var dates = monster.util.getDefaultRangeDates(defaultDateRange);
fromDate = dates.from;
toDate = dates.to;
}
var dataTemplate = {
timezone: 'GMT' + jstz.determine_timezone().offset(),
type: type || 'today',
fromDate: fromDate,
toDate: toDate,
showFilteredDates: ['thisMonth', 'thisWeek'].indexOf(type) >= 0,
showReport: monster.config.whitelabel.callReportEmail ? true : false
};
self.callLogsGetCdrs(fromDate, toDate, function(cdrs, nextStartKey) {
cdrs = self.callLogsFormatCdrs(cdrs);
dataTemplate.cdrs = cdrs;
template = $(self.getTemplate({
name: 'layout',
data: dataTemplate,
submodule: 'callLogs'
}));
monster.ui.tooltips(template);
if (cdrs && cdrs.length) {
var cdrsTemplate = $(self.getTemplate({
name: 'cdrsList',
data: {
cdrs: cdrs,
showReport: monster.config.whitelabel.callReportEmail ? true : false
},
submodule: 'callLogs'
}));
template.find('.call-logs-grid .grid-row-container')
.append(cdrsTemplate);
}
var optionsDatePicker = {
container: template,
range: maxDateRange
};
monster.ui.initRangeDatepicker(optionsDatePicker);
template.find('#startDate').datepicker('setDate', fromDate);
template.find('#endDate').datepicker('setDate', toDate);
if (!nextStartKey) {
template.find('.call-logs-loader').hide();
}
self.callLogsBindEvents({
template: template,
cdrs: cdrs,
fromDate: fromDate,
toDate: toDate,
nextStartKey: nextStartKey
});
monster.ui.tooltips(template);
parent
.empty()
.append(template);
callback && callback();
});
},
callLogsBindEvents: function(params) {
var self = this,
template = params.template,
cdrs = params.cdrs,
fromDate = params.fromDate,
toDate = params.toDate,
startKey = params.nextStartKey;
setTimeout(function() {
template.find('.search-query').focus();
});
template.find('.apply-filter').on('click', function(e) {
var fromDate = template.find('input.filter-from').datepicker('getDate'),
toDate = template.find('input.filter-to').datepicker('getDate');
self.callLogsRenderContent(template.parents('.right-content'), fromDate, toDate, 'custom');
});
template.find('.fixed-ranges button').on('click', function(e) {
var $this = $(this),
type = $this.data('type');
// We don't really need to do that, but it looks better to the user if we still remove/add the classes instantly.
template.find('.fixed-ranges button').removeClass('active');
$this.addClass('active');
if (type !== 'custom') {
// Without this, it doesn't look like we're refreshing the data.
// GOod way to solve it would be to separate the filters from the call logs view, and only refresh the call logs.
template.find('.call-logs-content').empty();
var dates = self.callLogsGetFixedDatesFromType(type);
self.callLogsRenderContent(template.parents('.right-content'), dates.from, dates.to, type);
} else {
template.find('.fixed-ranges-date').hide();
template.find('.custom-range').addClass('active');
}
});
template.find('.download-csv').on('click', function(e) {
var fromDateTimestamp = monster.util.dateToBeginningOfGregorianDay(fromDate),
toDateTimestamp = monster.util.dateToEndOfGregorianDay(toDate),
url = self.apiUrl + 'accounts/' + self.accountId + '/cdrs?created_from=' + fromDateTimestamp + '&created_to=' + toDateTimestamp + '&paginate=false&accept=text/csv&auth_token=' + self.getAuthToken();
window.open(url, '_blank');
});
template.find('.search-div input.search-query').on('keyup', function(e) {
if (template.find('.grid-row-container .grid-row').length > 0) {
var searchValue = $(this).val().replace(/\|/g, '').toLowerCase(),
matchedResults = false;
if (searchValue.length <= 0) {
template.find('.grid-row-group').show();
matchedResults = true;
} else {
_.each(cdrs, function(cdr) {
var searchString = (cdr.date + '|' + cdr.fromName + '|' + cdr.fromNumber + '|' + cdr.toName + '|'
+ cdr.toNumber + '|' + cdr.hangupCause + '|' + cdr.id).toLowerCase(),
rowGroup = template.find('.grid-row.main-leg[data-id="' + cdr.id + '"]').parents('.grid-row-group');
if (searchString.indexOf(searchValue) >= 0) {
matchedResults = true;
rowGroup.show();
} else {
rowGroup.hide();
}
});
}
if (matchedResults) {
template.find('.grid-row.no-match').hide();
} else {
template.find('.grid-row.no-match').show();
}
}
});
template.on('click', '.grid-row.main-leg', function(e) {
var $this = $(this),
rowGroup = $this.parents('.grid-row-group'),
callId = $this.data('id'),
extraLegs = rowGroup.find('.extra-legs');
if (rowGroup.hasClass('open')) {
rowGroup.removeClass('open');
extraLegs.slideUp();
} else {
// Reset all slidedDown legs
template.find('.grid-row-group').removeClass('open');
template.find('.extra-legs').slideUp();
// Slide down current leg
rowGroup.addClass('open');
extraLegs.slideDown();
if (!extraLegs.hasClass('data-loaded')) {
self.callLogsGetLegs(callId, function(cdrs) {
var formattedCdrs = self.callLogsFormatCdrs(cdrs);
rowGroup.find('.extra-legs')
.empty()
.addClass('data-loaded')
.append($(self.getTemplate({
name: 'interactionLegs',
data: {
cdrs: formattedCdrs
},
submodule: 'callLogs'
})));
});
}
}
});
template.on('click', '.grid-cell.actions .details-cdr', function(e) {
e.stopPropagation();
var cdrId = $(this).parents('.grid-row').data('id');
self.callLogsShowDetailsPopup(cdrId);
});
template.on('click', '.grid-cell.report a', function(e) {
e.stopPropagation();
});
function loadMoreCdrs() {
var loaderDiv = template.find('.call-logs-loader'),
cdrsTemplate;
if (startKey) {
loaderDiv.toggleClass('loading');
loaderDiv.find('.loading-message > i').toggleClass('fa-spin');
self.callLogsGetCdrs(fromDate, toDate, function(newCdrs, nextStartKey) {
newCdrs = self.callLogsFormatCdrs(newCdrs);
cdrsTemplate = $(self.getTemplate({
name: 'cdrsList',
data: {
cdrs: newCdrs,
showReport: monster.config.whitelabel.callReportEmail ? true : false
},
submodule: 'callLogs'
}));
startKey = nextStartKey;
if (!startKey) {
template.find('.call-logs-loader').hide();
}
template.find('.call-logs-grid .grid-row-container').append(cdrsTemplate);
cdrs = cdrs.concat(newCdrs);
var searchInput = template.find('.search-div input.search-query');
if (searchInput.val()) {
searchInput.keyup();
}
loaderDiv.toggleClass('loading');
loaderDiv.find('.loading-message > i').toggleClass('fa-spin');
}, startKey);
} else {
loaderDiv.hide();
}
}
template.find('.call-logs-grid').on('scroll', function(e) {
var $this = $(this);
if ($this.scrollTop() === $this[0].scrollHeight - $this.innerHeight()) {
loadMoreCdrs();
}
});
template.find('.call-logs-loader:not(.loading) .loader-message').on('click', function(e) {
loadMoreCdrs();
});
},
// Function built to return JS Dates for the fixed ranges.
callLogsGetFixedDatesFromType: function(type) {
var self = this,
from = new Date(),
to = new Date();
if (type === 'thisWeek') {
// First we need to know how many days separate today and monday.
// Since Sunday is 0 and Monday is 1, we do this little substraction to get the result.
var day = from.getDay(),
countDaysFromMonday = (day || 7) - 1;
from.setDate(from.getDate() - countDaysFromMonday);
} else if (type === 'thisMonth') {
from.setDate(1);
}
return {
from: from,
to: to
};
},
callLogsGetCdrs: function(fromDate, toDate, callback, pageStartKey) {
var self = this,
fromDateTimestamp = monster.util.dateToBeginningOfGregorianDay(fromDate),
toDateTimestamp = monster.util.dateToEndOfGregorianDay(toDate),
filters = {
'created_from': fromDateTimestamp,
'created_to': toDateTimestamp,
'page_size': 50
};
if (pageStartKey) {
filters.start_key = pageStartKey;
}
self.callApi({
resource: 'cdrs.listByInteraction',
data: {
accountId: self.accountId,
filters: filters
},
success: function(data, status) {
callback(data.data, data.next_start_key);
}
});
},
callLogsGetLegs: function(callId, callback) {
var self = this;
self.callApi({
resource: 'cdrs.listLegs',
data: {
accountId: self.accountId,
callId: callId
},
success: function(data) {
callback && callback(data.data);
}
});
},
callLogsFormatCdrs: function(cdrs) {
var self = this,
result = [],
deviceIcons = {
'cellphone': 'fa fa-phone',
'smartphone': 'icon-telicon-mobile-phone',
'landline': 'icon-telicon-home',
'mobile': 'icon-telicon-sprint-phone',
'softphone': 'icon-telicon-soft-phone',
'sip_device': 'icon-telicon-voip-phone',
'sip_uri': 'icon-telicon-voip-phone',
'fax': 'icon-telicon-fax',
'ata': 'icon-telicon-ata',
'unknown': 'fa fa-circle'
},
formatCdr = function(cdr) {
var date = cdr.hasOwnProperty('channel_created_time') ? monster.util.unixToDate(cdr.channel_created_time, true) : monster.util.gregorianToDate(cdr.timestamp),
shortDate = monster.util.toFriendlyDate(date, 'shortDate'),
time = monster.util.toFriendlyDate(date, 'time'),
durationMin = parseInt(cdr.duration_seconds / 60).toString(),
durationSec = (cdr.duration_seconds % 60 < 10 ? '0' : '') + (cdr.duration_seconds % 60),
hangupI18n = self.i18n.active().hangupCauses,
hangupHelp = '',
isOutboundCall = 'authorizing_id' in cdr && cdr.authorizing_id.length > 0;
// Only display help if it's in the i18n.
if (hangupI18n.hasOwnProperty(cdr.hangup_cause)) {
if (isOutboundCall && hangupI18n[cdr.hangup_cause].hasOwnProperty('outbound')) {
hangupHelp += hangupI18n[cdr.hangup_cause].outbound;
} else if (!isOutboundCall && hangupI18n[cdr.hangup_cause].hasOwnProperty('inbound')) {
hangupHelp += hangupI18n[cdr.hangup_cause].inbound;
}
}
var call = {
id: cdr.id,
callId: cdr.call_id,
timestamp: cdr.timestamp,
date: shortDate,
time: time,
fromName: cdr.caller_id_name,
fromNumber: cdr.caller_id_number || cdr.from.replace(/@.*/, ''),
toName: cdr.callee_id_name,
toNumber: cdr.callee_id_number || ('request' in cdr) ? cdr.request.replace(/@.*/, '') : cdr.to.replace(/@.*/, ''),
duration: durationMin + ':' + durationSec,
hangupCause: cdr.hangup_cause,
hangupHelp: hangupHelp,
isOutboundCall: isOutboundCall,
mailtoLink: 'mailto:' + monster.config.whitelabel.callReportEmail
+ '?subject=Call Report: ' + cdr.call_id
+ '&body=Please describe the details of the issue:%0D%0A%0D%0A'
+ '%0D%0A____________________________________________________________%0D%0A'
+ '%0D%0AAccount ID: ' + self.accountId
+ '%0D%0AFrom (Name): ' + (cdr.caller_id_name || '')
+ '%0D%0AFrom (Number): ' + (cdr.caller_id_number || cdr.from.replace(/@.*/, ''))
+ '%0D%0ATo (Name): ' + (cdr.callee_id_name || '')
+ '%0D%0ATo (Number): ' + (cdr.callee_id_number || ('request' in cdr) ? cdr.request.replace(/@.*/, '') : cdr.to.replace(/@.*/, ''))
+ '%0D%0ADate: ' + shortDate
+ '%0D%0ADuration: ' + durationMin + ':' + durationSec
+ '%0D%0AHangup Cause: ' + (cdr.hangup_cause || '')
+ '%0D%0ACall ID: ' + cdr.call_id
+ '%0D%0AOther Leg Call ID: ' + (cdr.other_leg_call_id || '')
+ '%0D%0AHandling Server: ' + (cdr.media_server || '')
};
if (cdr.hasOwnProperty('channel_created_time')) {
call.channelCreatedTime = cdr.channel_created_time;
}
if (cdr.hasOwnProperty('custom_channel_vars') && cdr.custom_channel_vars.hasOwnProperty('authorizing_id') && self.appFlags.callLogs.devices.hasOwnProperty(cdr.custom_channel_vars.authorizing_id)) {
var device = self.appFlags.callLogs.devices[cdr.custom_channel_vars.authorizing_id];
call.formatted = call.formatted || {};
call.formatted.deviceIcon = deviceIcons[device.device_type];
call.formatted.deviceTooltip = self.i18n.active().devices.types[device.device_type];
if (cdr.call_direction === 'inbound') {
call.formatted.fromDeviceName = device.name;
} else {
call.formatted.toDeviceName = device.name;
}
}
return call;
};
_.each(cdrs, function(v) {
result.push(formatCdr(v));
});
// In this automagic function... if field doesn't have channelCreateTime, it's because it's a "Main Leg" (legs listed on the first listing, not details)
// if it's a "main leg" we sort by descending timestamp.
// if it's a "detail leg", then it has a channelCreatedTime attribute set, and we sort on this as it's more precise. We sort it ascendingly so the details of the calls go from top to bottom in the UI
result.sort(function(a, b) {
return (a.hasOwnProperty('channelCreatedTime') && b.hasOwnProperty('channelCreatedTime')) ? (a.channelCreatedTime > b.channelCreatedTime ? 1 : -1) : (a.timestamp > b.timestamp ? -1 : 1);
});
return result;
},
callLogsShowDetailsPopup: function(callLogId) {
var self = this;
self.callApi({
resource: 'cdrs.get',
data: {
accountId: self.accountId,
cdrId: callLogId
},
success: function(data, status) {
var template = $(self.getTemplate({
name: 'detailsPopup',
submodule: 'callLogs'
}));
monster.ui.renderJSON(data.data, template.find('#jsoneditor'));
monster.ui.dialog(template, { title: self.i18n.active().callLogs.detailsPopupTitle });
},
error: function(data, status) {
monster.ui.alert('error', self.i18n.active().callLogs.alertMessages.getDetailsError);
}
});
},
callLogsListDevices: function(callback) {
var self = this;
self.callApi({
resource: 'device.list',
data: {
accountId: self.accountId,
filters: {
paginate: false
}
},
success: function(data) {
callback && callback(data.data);
}
});
}
};
return app;
});