contactcats/ang/crmContactcats.js
Rich Lott / Artful Robot 69d4c6b2e8 Fixes and tidies
2025-02-27 18:11:13 +00:00

206 lines
7.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

(function(angular, $, _) {
// Declare a list of dependencies.
angular.module("crmContactcats", CRM.angRequires("crmContactcats"));
angular.module("crmContactcats").component("crmContactCategorySettings", {
templateUrl: "~/crmContactcats/crmContactCategorySettings.html",
bindings: {
// things listed here become properties on the controller using
// values from attributes. @ means a fixed string is passed
// e.g. <crm-reset-password token='foo' />
// token: '@',
// & is special. In the child, call ctrl.onMyAction with an
// Object whose keys provide parameter names for the code in the parent.
// onMyAction: '&',
// '<' means one-way binding (parent»child) and I think is what you are
// supposed to use for components.
// v: '<'
},
controller: function($scope, $timeout, crmApi4, crmStatus, $document) {
var ts = ($scope.ts = CRM.ts(null)),
ctrl = this;
// this.$onInit gets run after the this controller is called, and after the bindings have been applied.
this.$onInit = async function() {
ctrl.dirty = 'pristine'; //pristine|dirty
ctrl.view = 'list';
ctrl.moveIdx = null;
ctrl.categoryToEdit = null;
ctrl.categoryDefinitions = null;
ctrl.categoryDefinitions = await crmApi4("ContactCategoryDefinition", 'get', { orderBy: { execution_order: 'ASC' }, withLabels: true });
if (ctrl.categoryDefinitions.count === 0) {
// First time.
ctrl.categoryDefinitions = [];
}
// Ensure we have the minimum of what we need.
if (!ctrl.categoryDefinitions.find(c => c.search_type === 'default')) {
// There's no default one.
ctrl.categoryDefinitions.push({
id: 0,
execution_order: ctrl.categoryDefinitions.length,
label: '9999 ' + ts('Other'),
search_type: 'default',
search_data: {},
color: '#666666',
icon: 'fa-person-circle-question',
description: ts('Any contact not matching any of the categories is assigned this category.'),
})
}
updateOrders();
console.log("Got", ctrl.categoryDefinitions);
$scope.$digest();
/**
* Presentation order is by label. Promoting "0123 label" type labels.
*
* We set execution_order to the array index to keep things simpler.
*/
function updateOrders() {
// re-set execution_order.
ctrl.categoryDefinitions.forEach((item, idx) => {
item.execution_order = idx;
});
const po = ctrl.categoryDefinitions.slice();
po.sort((a, b) => {
if (a.label < b.label) return -1;
if (a.label > b.label) return 1;
return (a.execution_order ?? 1) - (b.execution_order ?? 1);
});
ctrl.presentationOrder = po;
}
ctrl.edit = idx => {
if (idx === -1) {
// New item.
// Create a random dark colour
let [r, g, b] = [Math.random(), Math.random(), Math.random()],
min = Math.min(r, g, b),
range = Math.max(r, g, b) - min,
conv = (x) => ('0' + Math.ceil((x-min)/range * 128).toString(16)).replace(/^.*(..)$/, '$1');
// Create a blank
ctrl.categoryToEdit = {
id: 0,
execution_order: -1,
label: '',
search_type: 'search',
search_data: { saved_search_id: null },
color: '#' + [r, g, b].map(conv).join(''),
icon: '',
description: '',
};
}
else {
ctrl.categoryToEdit = Object.assign({}, JSON.parse(JSON.stringify(ctrl.categoryDefinitions[idx])));
}
ctrl.view = 'edit';
};
ctrl.moveTo = idx => {
let item = ctrl.categoryDefinitions.splice(ctrl.moveIdx, 1)[0];
if (idx > ctrl.moveIdx) {
idx--;
}
ctrl.categoryDefinitions.splice(idx, 0, item);
ctrl.moveIdx = null;
updateOrders();
ctrl.dirty = 'dirty';
};
ctrl.deleteCategory = idx => {
if (!confirm(ts(
'Confirm deleting category %1. You will lose history related to this category. Sure?',
{1: ctrl.categoryDefinitions[idx].label}
))) {
return;
}
ctrl.categoryDefinitions[idx].deleted = true;
ctrl.categoryToEdit = null;
updateOrders();
ctrl.view = 'list';
ctrl.dirty = 'dirty';
};
ctrl.updateEditedThing = () => {
const edited = ctrl.categoryToEdit;
// @todo validate, e.g.
if (!edited.label) {
alert("No name");
return;
}
const search_data = edited.search_data;
if (edited.search_type === 'group') {
// Only store what we need.
const {group_id} = search_data;
edited.search_data = {group_id};
}
else if (edited.search_type === 'search') {
const {saved_search_id} = search_data;
edited.search_data = {saved_search_id};
}
if (edited.execution_order === -1) {
// This is a new category, we need to insert it before the default one.
const newIdx = ctrl.categoryDefinitions.length - 1;
ctrl.categoryDefinitions.splice(newIdx, 0, edited);
}
else {
ctrl.categoryDefinitions[edited.execution_order] = edited;
}
ctrl.categoryToEdit = null;
updateOrders();
ctrl.dirty = 'dirty';
ctrl.view = 'list';
}
// We need to ensure the search_data object contains the fields required for the selected search_type
ctrl.fixSearchData = () => {
const search_data = ctrl.categoryToEdit.search_data;
if (ctrl.categoryToEdit.search_type === 'group') {
if (!('group_id' in search_data)) {
search_data.group_id = null;
}
}
else if (ctrl.categoryToEdit.search_type === 'search') {
if (!('saved_search_id' in search_data)) {
search_data.saved_search_id = null;
}
}
};
ctrl.save = () => {
if (!confirm(ts("Confirm saving changes to categories? Note that categories will not be fully applied until tomorrow."))) { return; }
// Handle deletions first.
const deletedIds = ctrl.categoryDefinitions.filter(d => d.deleted && d.id > 0).map(d => d.id);
const chain = Promise.resolve();
if (deletedIds.length) {
chain.then(() => crmApi4('ContactCategoryDefinition', 'delete', { where: [ ['id', 'IN', deletedIds] ] }));
}
// Now enact the deletions on our local model.
ctrl.categoryDefinitions = ctrl.categoryDefinitions.filter(d => !d.deleted);
// Tidy execution_order
updateOrders();
if (ctrl.categoryDefinitions.length) {
chain.then(() => crmApi4('ContactCategoryDefinition', 'save', {records:ctrl.categoryDefinitions}));
}
chain.then(() => {
ctrl.dirty = 'pristine';
})
crmStatus({ start: ts('Saving...'), success: ts('Saved')}, chain);
};
};
// this.$onChange = function(changes) {
// // changes is object keyed by name what '<' binding changed in
// // the parent (e.g. 'v'), and value is another obj with keys
// // something like previous, new, ...?...
// };
}
});
})(angular, CRM.$, CRM._);