mirror of
https://codeberg.org/artfulrobot/contactcats.git
synced 2025-06-25 12:58:05 +02:00
Main edit interface functinonal except save and execution order
This commit is contained in:
parent
4978eeb828
commit
b0983603e5
6 changed files with 346 additions and 72 deletions
|
@ -2,10 +2,44 @@
|
|||
crm-contact-category-settings ol {
|
||||
padding: 0;
|
||||
}
|
||||
crm-contact-category-settings ol li {
|
||||
crm-contact-category-settings li.deleted {
|
||||
opacity: 0.3;
|
||||
}
|
||||
/* @keyframes crm-contact-cats-move { */
|
||||
/* 0%, 90% {transform: none;} */
|
||||
/* 95% {transform: translateX(0.2em);} */
|
||||
/* 100% {transform: none;} */
|
||||
/* } */
|
||||
|
||||
crm-contact-category-settings .crm-contact-cats-move-target {
|
||||
transition: opacity 0.3s ease, transform 0.3s ease, display 0.001s allow-discrete, max-height 0.3s ease;
|
||||
transform-origin: top left;
|
||||
display: none;
|
||||
}
|
||||
crm-contact-category-settings .moving .crm-contact-cats-move-target {
|
||||
display: block;
|
||||
transform: none;
|
||||
opacity: 1;
|
||||
max-height: 5rem;
|
||||
@starting-style {
|
||||
transform: scaleY(0);
|
||||
opacity:0;
|
||||
max-height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
crm-contact-category-settings li.being-moved .panel {
|
||||
opacity: 0.8;
|
||||
}
|
||||
crm-contact-category-settings .panel {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
crm-contact-category-settings .panel-body {
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
padding: 0.5em 0;
|
||||
align-items: flex-end;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1em;
|
||||
}
|
||||
crm-contact-category-settings li label {
|
||||
|
|
|
@ -22,11 +22,109 @@
|
|||
// this.$onInit gets run after the this controller is called, and after the bindings have been applied.
|
||||
this.$onInit = async function() {
|
||||
ctrl.saved = false;
|
||||
ctrl.view = 'list';
|
||||
ctrl.moveIdx = null;
|
||||
ctrl.categoryToEdit = null;
|
||||
ctrl.categoryDefinitions = null;
|
||||
ctrl.categoryDefinitions = await crmApi4("ContactCategoryDefinition", 'get', { orderBy: { label: 'ASC' }, withLabels: true });
|
||||
|
||||
$scope.$digest();
|
||||
|
||||
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 = {
|
||||
idx: ctrl.categoryDefinitions.length,
|
||||
label: '',
|
||||
search_type: 'search',
|
||||
search_data: { saved_search_id: null },
|
||||
color: '#' + [r, g, b].map(conv).join(''),
|
||||
icon: '',
|
||||
description: '',
|
||||
};
|
||||
}
|
||||
else {
|
||||
ctrl.categoryToEdit = Object.assign({idx}, 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;
|
||||
};
|
||||
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;
|
||||
ctrl.view = 'list';
|
||||
};
|
||||
|
||||
ctrl.updateEditedThing = () => {
|
||||
// @todo validate, e.g.
|
||||
if (!ctrl.categoryToEdit.label) {
|
||||
alert("No name");
|
||||
return;
|
||||
}
|
||||
|
||||
const search_data = ctrl.categoryToEdit.search_data;
|
||||
console.log("search_data", search_data);
|
||||
if (ctrl.categoryToEdit.search_type === 'group') {
|
||||
// Only store what we need.
|
||||
const {group_id} = search_data;
|
||||
console.log("group_id", group_id, search_data);
|
||||
ctrl.categoryToEdit.search_data = {group_id};
|
||||
}
|
||||
else if (ctrl.categoryToEdit.search_type === 'search') {
|
||||
const {saved_search_id} = search_data;
|
||||
ctrl.categoryToEdit.search_data = {saved_search_id};
|
||||
}
|
||||
|
||||
const edited = ctrl.categoryToEdit;
|
||||
const idx = edited.idx;
|
||||
delete(edited.idx);
|
||||
ctrl.categoryDefinitions[idx] = edited;
|
||||
ctrl.categoryToEdit = null;
|
||||
ctrl.view = 'list';
|
||||
console.log("done editing");
|
||||
}
|
||||
|
||||
// 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 = async () => {
|
||||
if (!confirm(ts("Confirm saving changes to categories? Note that categories will not be fully applied until tomorrow."))) { return; }
|
||||
};
|
||||
|
||||
// ctrl.deleteRow = idx => {
|
||||
// ctrl.catmap.splice(idx, 1);
|
||||
// };
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
<div ng-if="$ctrl.categoryDefinitions === null">
|
||||
Loading...
|
||||
</div>
|
||||
<form ng-if="$ctrl.categoryDefinitions" crm-ui-id-scope>
|
||||
|
||||
<button ng-click="$ctrl.catmap.unshift({groupID: '', name:''})">
|
||||
<i class="crm-i fa-plus"></i> Add category
|
||||
</button>
|
||||
<form ng-if="$ctrl.categoryDefinitions"
|
||||
ng-show="$ctrl.view === 'list'"
|
||||
crm-ui-id-scope>
|
||||
|
||||
<!-- I can see use of
|
||||
presentation order/grouping
|
||||
|
@ -13,27 +11,139 @@
|
|||
longer title/description. "regular givers who..."
|
||||
-->
|
||||
|
||||
<ol class="crm-catmap">
|
||||
<li ng-repeat="(idx, row) in $ctrl.categoryDefinitions">
|
||||
|
||||
<span style="color: {{row.color ? '#' + row.color : 'inherit'}};" >
|
||||
<ol class="crm-catmap {{$ctrl.moveIdx !== null ? 'moving' : ''}}" >
|
||||
<li ng-repeat="(idx, row) in $ctrl.categoryDefinitions" class="{{idx === $ctrl.moveIdx ? 'being-moved' : ''}} {{row.deleted ? 'deleted' : ''}}">
|
||||
<!-- higher idx : {{idx}} moveIdx {{$ctrl.moveIdx}} -->
|
||||
<div class="crm-contact-cats-move-target" ng-show="(idx === 0 && $ctrl.moveIdx > 0)" >
|
||||
<button ng-click="$ctrl.moveTo(0)" >{{ts('Move to here')}}</button>
|
||||
</div>
|
||||
<div class=panel>
|
||||
<div class=panel-body>
|
||||
<span style="color: {{row.color ? row.color : 'inherit'}};" >
|
||||
<i ng-if="row.icon" class="crm-i {{row.icon}}" ></i>
|
||||
{{row.label}}
|
||||
</span>
|
||||
|
||||
<!-- button group? -->
|
||||
<span>
|
||||
<button ng-click="$ctrl.deleteRow(idx)">
|
||||
<i class="fa-trash crm-i"></i> Delete
|
||||
<button ng-if="$ctrl.moveIdx === null && !row.deleted" ng-click="$ctrl.edit(idx)" >
|
||||
<i class="fa-pencil crm-i"></i> {{ts('Edit')}}
|
||||
</button>
|
||||
<button ng-if="$ctrl.moveIdx === null && !row.deleted" ng-click="$ctrl.deleteCategory(idx)">
|
||||
<i class="fa-trash crm-i"></i> {{ts('Delete')}}
|
||||
</button>
|
||||
<button ng-if="$ctrl.moveIdx === null && row.deleted" ng-click="row.deleted = false;">
|
||||
<i class="fa-trash crm-i"></i> {{ts('Un-Delete')}}
|
||||
</button>
|
||||
<button ng-if="$ctrl.moveIdx === null && !row.deleted" ng-click="$ctrl.moveIdx = idx" >
|
||||
<i class="fa-pencil crm-i"></i> {{ts('Move')}}
|
||||
</button>
|
||||
<button ng-if="$ctrl.moveIdx !== null && idx === $ctrl.moveIdx" ng-click="$ctrl.moveIdx = null" >
|
||||
<i class="fa-pencil crm-i"></i> {{ts('Cancel Move')}}
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- lower idx : {{idx}} moveIdx {{$ctrl.moveIdx}} -->
|
||||
<div class="crm-contact-cats-move-target" ng-show="($ctrl.moveIdx < idx) || (idx +1 < $ctrl.moveIdx)" >
|
||||
<button ng-click="$ctrl.moveTo(idx+1)" >{{ts('Move to here')}}</button>
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<p>
|
||||
<button ng-click="$ctrl.edit(-1)"><i class="crm-i fa-add"></i> Add new category</button>
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
<p>
|
||||
<button ng-click="$ctrl.save()"><i class="crm-i fa-save"></i> Save</button>
|
||||
</p>
|
||||
|
||||
<div ng-if="$ctrl.saved" class="help">
|
||||
Categories saved. Contacts will be updated shortly (when the next Scheduled
|
||||
Job run happens).
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
<form ng-show="$ctrl.view === 'edit'"
|
||||
crm-ui-id-scope>
|
||||
<div>
|
||||
<h2 ng-if="$ctrl.categoryDefinitions.length > $ctrl.categoryToEdit.idx">{{ts('Edit category %1', {1 : $ctrl.categoryDefinitions[$ctrl.categoryToEdit.idx].label })}}</h2>
|
||||
<h2 ng-if="$ctrl.categoryDefinitions.length == $ctrl.categoryToEdit.idx">{{ts('Add new category')}}</h2>
|
||||
|
||||
<div crm-ui-field="{name: 'cc.label', title: ts('Category label'), required: 1}" >
|
||||
<input
|
||||
crm-ui-id="ci.label"
|
||||
name="label"
|
||||
ng-model="$ctrl.categoryToEdit.label"
|
||||
class="crm-form-text"
|
||||
placeholder="{{ts('0123 Super lovely supporters')}}"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div crm-ui-field="{name: 'cc.color', title: ts('Color')}" >
|
||||
<input
|
||||
crm-ui-id="ci.color"
|
||||
name="color"
|
||||
type="color"
|
||||
ng-model="$ctrl.categoryToEdit.color"
|
||||
class="crm-form-color"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div crm-ui-field="{name: 'cc.icon', title: ts('Icon')}" >
|
||||
<input
|
||||
crm-ui-id="cc.icon"
|
||||
crm-ui-icon-picker
|
||||
ng-model="$ctrl.categoryToEdit.icon"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div crm-ui-field="{name: 'cc.description', title: ts('Description')}" >
|
||||
<input
|
||||
crm-ui-id="ci.description"
|
||||
name="description"
|
||||
ng-model="$ctrl.categoryToEdit.description"
|
||||
class="crm-form-textarea"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div crm-ui-field="{name: 'cc.search_type', title: ts('How are contacts identified?')}" >
|
||||
<select
|
||||
crm-ui-id="ci.search_type"
|
||||
name="search_type"
|
||||
ng-model="$ctrl.categoryToEdit.search_type"
|
||||
ng-change="$ctrl.fixSearchData()"
|
||||
>
|
||||
<option value="search" >{{ts('Search Kit search')}}</option>
|
||||
<option value="group" >{{ts('Group')}}</option>
|
||||
<!-- future? <option value="sql_template" >{{ts('SQL template')}}</option> -->
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- fields for search kit -->
|
||||
<div ng-if="$ctrl.categoryToEdit.search_type === 'search'" crm-ui-field="{name:'cc.saved_search_id', title: ts('Search Kit search')}" >
|
||||
<input crm-ui-id='cc.saved_search_id' crm-entityref="{entity: 'SavedSearch', select: {allowClear:false}}"
|
||||
ng-model="$ctrl.categoryToEdit.search_data.saved_search_id" />
|
||||
</div>
|
||||
|
||||
<!-- fields for groups -->
|
||||
<div ng-if="$ctrl.categoryToEdit.search_type === 'group'" crm-ui-field="{name:'cc.group_id', title: ts('Group')}" >
|
||||
<input crm-ui-id='cc.group_id' crm-entityref="{entity: 'Group', select: {allowClear:false}}"
|
||||
ng-model="$ctrl.categoryToEdit.search_data.group_id" />
|
||||
</div>
|
||||
|
||||
|
||||
<!-- type=button means when you hit Enter in an input above this button is skipped while it looks for the first submit button. -->
|
||||
<button type=button ng-click="$ctrl.view = 'list'" >Cancel</button>
|
||||
<button ng-click="$ctrl.updateEditedThing()" >Done</button>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- <pre>{{ $ctrl.categoryToEdit }}</pre> -->
|
||||
|
|
83
managed/CustomGroup_Category_changes.mgd.php
Normal file
83
managed/CustomGroup_Category_changes.mgd.php
Normal file
|
@ -0,0 +1,83 @@
|
|||
<?php
|
||||
use CRM_Contactcats_ExtensionUtil as E;
|
||||
|
||||
return [
|
||||
[
|
||||
'name' => 'CustomGroup_Category_changes',
|
||||
'entity' => 'CustomGroup',
|
||||
'cleanup' => 'unused',
|
||||
'update' => 'unmodified',
|
||||
'params' => [
|
||||
'version' => 4,
|
||||
'values' => [
|
||||
'name' => 'Category_changes',
|
||||
'title' => E::ts('Category changes'),
|
||||
'extends' => 'Activity',
|
||||
'extends_entity_column_value:name' => ['changed_contact_category'],
|
||||
'style' => 'Inline',
|
||||
'help_pre' => E::ts(''),
|
||||
'help_post' => E::ts(''),
|
||||
'weight' => 18,
|
||||
'collapse_adv_display' => TRUE,
|
||||
'created_date' => '2025-02-20 13:59:06',
|
||||
'is_public' => FALSE,
|
||||
'icon' => '',
|
||||
],
|
||||
'match' => ['name'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'CustomGroup_Category_changes_CustomField_Previous_Category',
|
||||
'entity' => 'CustomField',
|
||||
'cleanup' => 'unused',
|
||||
'update' => 'unmodified',
|
||||
'params' => [
|
||||
'version' => 4,
|
||||
'values' => [
|
||||
'custom_group_id.name' => 'Category_changes',
|
||||
'name' => 'Previous_Category',
|
||||
'label' => E::ts('Previous Category'),
|
||||
'data_type' => 'EntityReference',
|
||||
'html_type' => 'Autocomplete-Select',
|
||||
'is_searchable' => TRUE,
|
||||
'is_view' => TRUE,
|
||||
'text_length' => 255,
|
||||
'note_columns' => 60,
|
||||
'note_rows' => 4,
|
||||
'column_name' => 'previous_category_117',
|
||||
'fk_entity' => 'ContactCategoryDefinition',
|
||||
],
|
||||
'match' => [
|
||||
'name',
|
||||
'custom_group_id',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'CustomGroup_Category_changes_CustomField_New_Category',
|
||||
'entity' => 'CustomField',
|
||||
'cleanup' => 'unused',
|
||||
'update' => 'unmodified',
|
||||
'params' => [
|
||||
'version' => 4,
|
||||
'values' => [
|
||||
'custom_group_id.name' => 'Category_changes',
|
||||
'name' => 'New_Category',
|
||||
'label' => E::ts('New Category'),
|
||||
'data_type' => 'EntityReference',
|
||||
'html_type' => 'Autocomplete-Select',
|
||||
'is_searchable' => TRUE,
|
||||
'is_view' => TRUE,
|
||||
'text_length' => 255,
|
||||
'note_columns' => 60,
|
||||
'note_rows' => 4,
|
||||
'column_name' => 'new_category_118',
|
||||
'fk_entity' => 'ContactCategoryDefinition',
|
||||
],
|
||||
'match' => [
|
||||
'name',
|
||||
'custom_group_id',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
|
@ -3,14 +3,14 @@ use CRM_Contactcats_ExtensionUtil as E;
|
|||
|
||||
return [
|
||||
[
|
||||
'name' => 'OptionValue_Changed_Contact_Category',
|
||||
'name' => 'OptionValue_changed_contact_category',
|
||||
'entity' => 'OptionValue',
|
||||
'cleanup' => 'unused',
|
||||
'update' => 'unmodified',
|
||||
'params' => [
|
||||
'version' => 4,
|
||||
'values' => [
|
||||
'option_group_id:name' => 'activity_type',
|
||||
'option_group_id.name' => 'activity_type',
|
||||
'label' => E::ts('Changed Contact Category'),
|
||||
'name' => 'changed_contact_category',
|
||||
'icon' => 'fa-tags',
|
|
@ -1,51 +0,0 @@
|
|||
<?php
|
||||
use CRM_Contactcats_ExtensionUtil as E;
|
||||
|
||||
return [
|
||||
[
|
||||
'name' => 'OptionGroup_contact_categories',
|
||||
'entity' => 'OptionGroup',
|
||||
'cleanup' => 'unused',
|
||||
'update' => 'unmodified',
|
||||
'params' => [
|
||||
'version' => 4,
|
||||
'values' => [
|
||||
'name' => 'contact_categories',
|
||||
'title' => E::ts('Contact Categories'),
|
||||
'data_type' => 'Integer',
|
||||
'is_reserved' => FALSE,
|
||||
'is_active' => TRUE,
|
||||
'option_value_fields' => [
|
||||
'name',
|
||||
'label',
|
||||
'description',
|
||||
'weight',
|
||||
],
|
||||
],
|
||||
'match' => [
|
||||
'name',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'OptionGroup_contact_categories_OptionValue_Default',
|
||||
'entity' => 'OptionValue',
|
||||
'cleanup' => 'unused',
|
||||
'update' => 'unmodified',
|
||||
'params' => [
|
||||
'version' => 4,
|
||||
'values' => [
|
||||
'option_group_id.name' => 'contact_categories',
|
||||
'label' => E::ts('Default'),
|
||||
'is_active' => TRUE,
|
||||
'value' => '0',
|
||||
'name' => 'default',
|
||||
],
|
||||
'match' => [
|
||||
'option_group_id',
|
||||
'name',
|
||||
'value',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
Loading…
Add table
Add a link
Reference in a new issue