Add Category history saved search and searchkit display and afform for contact sum

This commit is contained in:
Rich Lott / Artful Robot 2025-03-03 11:55:12 +00:00
parent 93be452053
commit 142479d861
7 changed files with 228 additions and 20 deletions

View file

@ -0,0 +1,3 @@
<div af-fieldset="">
<crm-search-display-contact-cat search-name="Category_history" display-name="Category_history_Category_1" filters="{'Activity_ActivityContact_Contact_01.id': options.contact_id}"></crm-search-display-contact-cat>
</div>

View file

@ -0,0 +1,31 @@
{
"type": "search",
"requires": null,
"entity_type": null,
"join_entity": null,
"title": "Category",
"description": null,
"placement": [
"contact_summary_block"
],
"summary_contact_type": [
"Individual"
],
"summary_weight": 1,
"icon": "fa-user-tag",
"server_route": "",
"is_public": false,
"permission": [
"access CiviCRM"
],
"permission_operator": "AND",
"redirect": null,
"submit_enabled": true,
"submit_limit": null,
"create_submission": false,
"manual_processing": false,
"allow_verification_by_email": false,
"email_confirmation_template_id": null,
"navigation": null,
"modified_date": "2025-03-03 11:26:32"
}

View file

@ -15,8 +15,11 @@ return [
'partials' => [ 'partials' => [
'ang/crmSearchDisplayContactCat', 'ang/crmSearchDisplayContactCat',
], ],
'requires' => ['crmUi', 'crmUtil', 'ngRoute'], 'requires' => ['crmSearchDisplay', 'crmSearchTasks', 'crmUi', 'crmUtil', 'ngRoute'],
'settings' => [], 'settings' => [],
'basePages' => ['civicrm/search', 'civicrm/admin/search'], 'basePages' => ['civicrm/search', 'civicrm/admin/search', 'civicrm/contact/view'],
'exports' => ['crmSearchAdminDisplayContactCat', 'crmSearchDisplayContactCat'], 'exports' => [
'crm-search-admin-display-contact-cat' => 'E',
'crm-search-display-contact-cat' => 'E',
],
]; ];

View file

@ -1 +1,4 @@
/* Add any CSS rules for Angular module "crmContactcats" */ /* Add any CSS rules for Angular module "crmContactcats" */
.crm-contact-page .crm-search-display-contact-cat {
padding: var(--crm-dash-block-padding);
}

View file

@ -1,10 +1,11 @@
console.log(123); console.log('crmSearchDisplayContactCat.js loaded');
// see https://docs.civicrm.org/dev/en/latest/searchkit/displays/ // see https://docs.civicrm.org/dev/en/latest/searchkit/displays/
(function(angular, $, _) { (function(angular, $, _) {
// Declare a list of dependencies. // Declare a list of dependencies.
angular.module('crmSearchDisplayContactCat', CRM.angRequires('crmSearchDisplayContactCat')); angular.module('crmSearchDisplayContactCat', CRM.angRequires('crmSearchDisplayContactCat'));
// angular.module('crmContactcats').component(); // angular.module('crmContactcats').component();
console.log('crmSearchDisplayContactCat module registered');
// This is to be the display // This is to be the display
// NOTE: the component name is {CamelCaseNameFromOptionValue} // NOTE: the component name is {CamelCaseNameFromOptionValue}
// Standard seems to be to name crmSearchDisplay{YourName} // Standard seems to be to name crmSearchDisplay{YourName}
@ -20,21 +21,60 @@ console.log(123);
totalCount: '=?' totalCount: '=?'
}, },
// controller: function($scope, $timeout, crmApi4, crmStatus, $document) { // controller: function($scope, $timeout, crmApi4, crmStatus, $document) {
controller: function($scope, $element, searchDisplayBaseTrait, searchDisplayTasksTrait) { controller: function($scope, $element, crmApi4, searchDisplayBaseTrait, searchDisplayTasksTrait) {
console.log('crmSearchDisplayContactCat controller called');
// Mix in required traits // Mix in required traits
ctrl = angular.extend(this, _.cloneDeep(searchDisplayBaseTrait), _.cloneDeep(searchDisplayTasksTrait)); ctrl = angular.extend(this, _.cloneDeep(searchDisplayBaseTrait), _.cloneDeep(searchDisplayTasksTrait));
console.log("hello display"); console.log("hello display");
var ts = ($scope.ts = CRM.ts(null)), var ts = ($scope.ts = CRM.ts(null)),
ctrl = this; ctrl = this;
ctrl.catDefs = null;
ctrl.resultsVM = {};
ctrl.n = 1;
// this.$onInit gets run after the this controller is called, and after the bindings have been applied. // this.$onInit gets run after the this controller is called, and after the bindings have been applied.
this.$onInit = function() { this.$onInit = function() {
console.log("calling initializeDisplay"); console.log("calling initializeDisplay");
// Grab all the category definitions
crmApi4('ContactCategoryDefinition', 'get', {
orderBy: {presentation_order: 'ASC'}
}, 'id').then(r => { ctrl.catDefs = r; console.log('catDefs', ctrl.catDefs); ctrl.n++; });
this.initializeDisplay($scope, $element); this.initializeDisplay($scope, $element);
}; };
$scope.$watch('$ctrl.results', () => { ctrl.n++; }, true);
$scope.$watch('$ctrl.n', () => {
console.log("watch n running");
if (!ctrl.catDefs || !ctrl.results?.count) {
console.log("...waiting for all the data we need");
ctrl.ourData = {
rows: [],
count: 0,
};
return ctrl.n;
}
const rows = ctrl.results.map((row) => {
const d = (new Date(row.data.activity_date_time.replace(' ', 'T'))).toLocaleString('UTC', {
weekday: 'short',
day: 'numeric',
month: 'short',
year: 'numeric',
});
return {
contact_id: row.data['Activity_ActivityContact_Contact_01.id'],
new: ctrl.catDefs[row.data['Category_changes.new_category_id']],
when: d,
};
});
ctrl.ourData = {rows, count:rows.length};
console.log("new ourData", ctrl.ourData);
return ctrl.n;
});
} }
}); });
console.log('crmSearchDisplayContactCat registering admin bit');
// This is to be the admin settings for the display // This is to be the admin settings for the display
// NOTE: the component name is 'searchAdminDisplay'{CamelCaseValueFromOptionValue} // NOTE: the component name is 'searchAdminDisplay'{CamelCaseValueFromOptionValue}
angular.module("crmSearchDisplayContactCat").component("searchAdminDisplayContactCat", { angular.module("crmSearchDisplayContactCat").component("searchAdminDisplayContactCat", {
@ -48,7 +88,7 @@ console.log(123);
parent: '^crmSearchAdminDisplay' parent: '^crmSearchAdminDisplay'
}, },
controller: function($scope, searchMeta, crmUiHelp) { controller: function($scope, searchMeta, crmUiHelp) {
console.log("hello admin"); console.log("hello admin controller");
let ctrl = this; let ctrl = this;
// var ts = ($scope.ts = CRM.ts(null)), // var ts = ($scope.ts = CRM.ts(null)),
// ctrl = this; // ctrl = this;
@ -69,5 +109,6 @@ console.log(123);
ctrl.parent.initColumns({}); ctrl.parent.initColumns({});
}; };
}}); }});
console.log('crmSearchDisplayContactCat ends');
})(angular, CRM.$, CRM._); })(angular, CRM.$, CRM._);

View file

@ -1,18 +1,35 @@
<div class="crm-search-display crm-search-display-contact-cat"> <div class="crm-search-display crm-search-display-contact-cat">
<p>This is the crmSearchDisplayContactCat thing</p> <!-- <div class="form-inline"> -->
<div class="form-inline"> <!-- <div class="btn-group" ng-include="'~/crmSearchDisplay/SearchButton.html'" ></div> -->
<div class="btn-group" ng-include="'~/crmSearchDisplay/SearchButton.html'" ></div> <!-- <div class="form-group pull-right" ng-include="'~/crmSearchDisplay/toolbar.html'" ng-if="$ctrl.toolbar"></div> -->
<div class="form-group pull-right" ng-include="'~/crmSearchDisplay/toolbar.html'" ng-if="$ctrl.toolbar"></div> <!-- </div> -->
</div>
<ol style="list-style: disc;"> <div>
<li ng-repeat="(rowIndex, row) in $ctrl.results" class="{{:: row.cssClass }}"> <!-- output the current category -->
<span style="color:{{row.data['ContactCategory_ContactCategoryDefinition_category_definition_id_01.color']}};" > <details class="">
<i class="crm-i {{row.data['ContactCategory_ContactCategoryDefinition_category_definition_id_01.icon']}}" ></i> <summary>
{{row.data['category_definition_id:label']}} {{ts('Category')}}:
<span ng-if="!$ctrl.ourData.count" >{{ts('Loading')}}</span>
<span ng-if="$ctrl.ourData.count" style="color:{{$ctrl.ourData.rows[0].new.color}};">
<i class="crm-i {{$ctrl.ourData.rows[0].new.icon}}" ></i>
{{$ctrl.ourData.rows[0].new.label}}
</span>
</summary>
<div class="crm-accordion-body" ng-if="$ctrl.ourData.count">
{{ts('Category History')}}
<!-- output category history -->
<ol>
<li ng-repeat="(rowIndex, row) in $ctrl.ourData.rows" class="{{:: row.cssClass }}">
{{row.when}} → <span style="color:{{row.new.color}};">
<i class="crm-i {{row.new.icon}}" ></i>
{{row.new.label}}
</span> </span>
</li> </li>
</ol> </ol>
<div ng-include="'~/crmSearchDisplay/Pager.html'"></div>
</div>
</details>
</div>
<!-- <div ng-include="'~/crmSearchDisplay/Pager.html'"></div> -->
</div> </div>

View file

@ -0,0 +1,110 @@
<?php
use CRM_Contactcats_ExtensionUtil as E;
return [
[
'name' => 'SavedSearch_Category_history',
'entity' => 'SavedSearch',
'cleanup' => 'unused',
'update' => 'unmodified',
'params' => [
'version' => 4,
'values' => [
'name' => 'Category_history',
'label' => E::ts('Category history'),
'api_entity' => 'Activity',
'api_params' => [
'version' => 4,
'select' => [
'Activity_ActivityContact_Contact_01.id',
'id',
'Activity_ActivityContact_Contact_01.sort_name',
'Category_changes.previous_category_id',
'Category_changes.new_category_id',
'activity_date_time',
],
'orderBy' => [],
'where' => [
[
'activity_type_id:name',
'=',
'changed_contact_category',
],
],
'groupBy' => [],
'join' => [
[
'Contact AS Activity_ActivityContact_Contact_01',
'INNER',
'ActivityContact',
[
'id',
'=',
'Activity_ActivityContact_Contact_01.activity_id',
],
[
'Activity_ActivityContact_Contact_01.record_type_id:name',
'=',
'"Activity Targets"',
],
],
],
'having' => [],
],
],
'match' => ['name'],
],
],
[
'name' => 'SavedSearch_Category_history_SearchDisplay_Category_history_Category_1',
'entity' => 'SearchDisplay',
'cleanup' => 'unused',
'update' => 'unmodified',
'params' => [
'version' => 4,
'values' => [
'name' => 'Category_history_Category_1',
'label' => E::ts('Category history Category 1'),
'saved_search_id.name' => 'Category_history',
'type' => 'contact-cat',
'settings' => [
'something' => 'nothing',
'limit' => 50,
'sort' => [],
'pager' => [],
'columns' => [
[
'type' => 'field',
'key' => 'Activity_ActivityContact_Contact_01.id',
'dataType' => 'Integer',
],
[
'type' => 'field',
'key' => 'id',
'dataType' => 'Integer',
],
[
'type' => 'field',
'key' => 'Activity_ActivityContact_Contact_01.sort_name',
'dataType' => 'String',
],
[
'type' => 'field',
'key' => 'Category_changes.previous_category_id',
'dataType' => 'Integer',
],
[
'type' => 'field',
'key' => 'Category_changes.new_category_id',
'dataType' => 'Integer',
],
],
],
],
'match' => [
'saved_search_id',
'name',
],
],
],
];