diff --git a/app/src/Lib/Traits/IndexQueryTrait.php b/app/src/Lib/Traits/IndexQueryTrait.php index b62d65c11..8a6eb8f64 100644 --- a/app/src/Lib/Traits/IndexQueryTrait.php +++ b/app/src/Lib/Traits/IndexQueryTrait.php @@ -159,7 +159,11 @@ public function getIndexQuery(bool $pickerMode = false, array $requestParams = [ $this->viewBuilder() ->getVar('vv_tz')); - // Pass any additional field filter confiration in the view + // Extra View Variables + foreach ($table->getViewVars() as $key => $variable) { + $this->set($key, $variable); + } + // Pass any additional field filter configuration in the view $this->set('vv_searchable_attributes_extras', $table->getSearchFiltersExtras()); if(!empty($searchableAttributes)) { $this->set('vv_searchable_attributes', $searchableAttributes); diff --git a/app/src/Lib/Traits/SearchFilterTrait.php b/app/src/Lib/Traits/SearchFilterTrait.php index 7827e6593..6b266a0b8 100644 --- a/app/src/Lib/Traits/SearchFilterTrait.php +++ b/app/src/Lib/Traits/SearchFilterTrait.php @@ -52,6 +52,12 @@ trait SearchFilterTrait { */ private array $searchFiltersExtras = []; + /** + * List of view Vars + * @var array + */ + private array $viewVars = []; + /** * Optional filter configuration that dictates display state and allows for related models * @@ -135,7 +141,7 @@ public function addJoins(Query $query, string $attribute, ServerRequest $request * @since COmanage Registry v5.0.0 */ public function constructDateComparisonClause(QueryExpression $exp, string $attributeWithModelPrefix, array $dates): QueryExpression { - // Both are empty, just return + // Both are empty, return if (empty($dates[0]) && empty($dates[1])) { return $exp; } @@ -243,9 +249,22 @@ public function getSearchableAttributes(string $controller, string $vv_tz=null): } $filterType = $f['type'] ?? 'string'; + // Custom boolean use cases if(\in_array($f['type'], ['isNull', 'isNotNull'])) { $filterType = 'boolean'; } + // Picker configuration + if(isset($f['picker'])) { + $autocompleteArgs = [ + 'type' => 'default', + 'fieldName' => $field, + 'personType' => $f['picker']['type'], + 'htmlId' => $field, // This is the input ID + 'viewConfigParameters' => $f['picker']['configuration'] + ]; + $this->viewVars['vv_autocomplete_arguments'] = $autocompleteArgs; + } + $this->searchFilters[$field] = [ 'type' => $filterType, 'label' => $f['label'] ?? StringUtilities::columnKey($fieldName, $field, $vv_tz, true), @@ -256,7 +275,7 @@ public function getSearchableAttributes(string $controller, string $vv_tz=null): } foreach ($this->filterMetadataFields() as $column => $type) { - // If the column is an array then we are accessing the Metadata fields. Skip + // If the column is an array, then we are accessing the Metadata fields. Skip if(is_array($type)) { continue; } @@ -323,4 +342,13 @@ public function getSearchFiltersExtras(): array return $this->searchFiltersExtras; } + /** + * Get View Vars + * + * @since COmanage Registry v5.0.0 + */ + public function getViewVars(): array + { + return $this->viewVars; + } } diff --git a/app/src/Model/Table/GroupsTable.php b/app/src/Model/Table/GroupsTable.php index a91ea9e38..4326cea6f 100644 --- a/app/src/Model/Table/GroupsTable.php +++ b/app/src/Model/Table/GroupsTable.php @@ -145,6 +145,16 @@ public function initialize(array $config): void { 'groupTypes' => [ 'type' => 'enum', 'class' => 'GroupTypeEnum' + ], + // Required for peoplePicker + 'cosettings' => [ + 'type' => 'auxiliary', + 'model' => 'CoSettings' + ], + // Required for peoplePicker + 'types' => [ + 'type' => 'auxiliary', + 'model' => 'Types' ] ]); @@ -154,6 +164,22 @@ public function initialize(array $config): void { 'model' => 'Identifiers', 'active' => true, 'order' => 4 + ], + 'person_id' => [ + 'type' => 'integer', + 'model' => 'GroupMembers', + 'active' => true, + 'order' => 5, + 'picker' => [ + 'type' => 'person', + 'configuration' => [ + // For the Groups Filtering block we want to + // pick/GET from the entire CO pool of people + 'action' => 'GET', + // The co configuration will fall throught the default configuration + 'for' => 'co' + ] + ] ] ]); diff --git a/app/src/View/Helper/FieldHelper.php b/app/src/View/Helper/FieldHelper.php index 958d17005..544fbb7cd 100644 --- a/app/src/View/Helper/FieldHelper.php +++ b/app/src/View/Helper/FieldHelper.php @@ -168,6 +168,40 @@ public function calculateLabelAndDescription(string $fieldName): array return [$label, $desc]; } + /** + * Calculate the list of classes for the li element + * + * @return string + */ + public function calculateLiClasses(): string + { + $fieldName = $this->getView()->get('fieldName'); + $vv_field_arguments = $this->getView()->get('vv_field_arguments'); + + // Class calculation by field Type + $classes = match ($this->getFieldType($fieldName)) { + 'date', + 'datetime', + 'timestamp' => 'fields-datepicker ', + default => '' + }; + + // Class calculation by field name + $classes .= match ($fieldName) { + 'source_record' => 'source-record ', + 'retry_interval', + 'login' => 'subfield ', + default => '' + }; + + // Class calculation by type of Info Div + if(isset($vv_field_arguments['autocomplete'])) { + $classes .= 'fields-people-autocomplete '; + } + + return $classes; + } + /** * Emit a date/time form control. * This is a wrapper function for $this->control() diff --git a/app/src/View/Helper/FilterHelper.php b/app/src/View/Helper/FilterHelper.php index 1cd59b602..5c7b169ae 100644 --- a/app/src/View/Helper/FilterHelper.php +++ b/app/src/View/Helper/FilterHelper.php @@ -30,6 +30,7 @@ namespace App\View\Helper; use Cake\Collection\Collection; +use Cake\ORM\TableRegistry; use Cake\Utility\{Inflector, Hash}; use Cake\View\Helper; @@ -157,4 +158,21 @@ public function getHiddenFields(): array ->filter(fn($value, $key) => !\in_array($key, $searchable_parameters, true) && $key != 'page') ->toArray(); } + + /** + * Construct Full Name from Person ID + * + * @param int $personId + * + * @return string + */ + public function getFullName(int $personId): string + { + if(empty($personId)) { + return ''; + } + $ModelTable = TableRegistry::getTableLocator()->get('Names'); + $person = $ModelTable->primaryName($personId); + return "{$person->given} {$person->family}"; + } } \ No newline at end of file diff --git a/app/templates/element/filter/filter.php b/app/templates/element/filter/filter.php index aba4149e8..cff0d99d7 100644 --- a/app/templates/element/filter/filter.php +++ b/app/templates/element/filter/filter.php @@ -27,8 +27,23 @@ declare(strict_types = 1); -use Cake\Utility\Inflector; +?> + + +name = Models $modelsName = $this->name; @@ -68,8 +83,17 @@ $options) { $elementArguments = compact('options', 'key'); + // Set in SearchfilterTrait.php + if($vv_autocomplete_arguments['fieldName'] === $key) { + // Picker is a custom type. + // This is why we calculate it here, and we + // only use it to pick the correct element + $options['type'] = 'picker'; + } + print match($options['type']) { 'date' => $this->element('filter/dateSingle', $elementArguments), + 'picker' => $this->element('filter/peoplePicker', $elementArguments), default => $this->element('filter/default', $elementArguments), }; } diff --git a/app/templates/element/filter/peoplePicker.php b/app/templates/element/filter/peoplePicker.php new file mode 100644 index 000000000..5d6fc6f2f --- /dev/null +++ b/app/templates/element/filter/peoplePicker.php @@ -0,0 +1,65 @@ +Filter->calculateFieldParams($key, $label); +if(!empty($formParams['value']) && $key == 'person_id') { + $formParams['fullName'] = $this->Filter->getFullName((int)$formParams['value']); +} +$vv_autocomplete_arguments['formParams'] = $formParams; + +?> + +
+ element('peopleAutocomplete', $vv_autocomplete_arguments) ?> +
\ No newline at end of file diff --git a/app/templates/element/filter/topButtons.php b/app/templates/element/filter/topButtons.php index 93baf9a05..ee318888b 100644 --- a/app/templates/element/filter/topButtons.php +++ b/app/templates/element/filter/topButtons.php @@ -41,10 +41,12 @@ $populated_vvar = lcfirst(Inflector::pluralize(Inflector::camelize($key))); $button_label = 'Range'; if(isset($$populated_vvar) && isset($$populated_vvar[$params])) { + // Get label name from AutoViewPopulated vars $button_label = $$populated_vvar[$params]; } elseif(!is_array($params)) { $button_label = $params; - if(isset($vv_searchable_attributes_extras)) { + // Extras use case + if(!empty($vv_searchable_attributes_extras)) { $flattenedSearchableAttributesExtras = Hash::flatten($vv_searchable_attributes_extras); $filteredFlattenedSearchableAttributesExtras = array_filter( $flattenedSearchableAttributesExtras, @@ -54,6 +56,8 @@ if(!empty($filteredFlattenedSearchableAttributesExtras)) { $button_label = array_pop($filteredFlattenedSearchableAttributesExtras); } + } elseif ($key == 'person_id') { + $button_label = $this->Filter->getFullName((int)$params); } } diff --git a/app/templates/element/form/listItem.php b/app/templates/element/form/listItem.php index 2774f259a..050321233 100644 --- a/app/templates/element/form/listItem.php +++ b/app/templates/element/form/listItem.php @@ -38,28 +38,6 @@ $fieldName = $arguments['fieldName']; $this->set('vv_field_arguments', $arguments); -// Class calculation by field Type -$classes = match ($this->Field->getFieldType($fieldName)) { - 'date', - 'datetime', - 'timestamp' => 'fields-datepicker ', - default => '' -}; - -// Class calculation by field name -$classes .= match ($fieldName) { - 'source_record' => 'source-record ', - 'retry_interval', - 'login' => 'subfield ', - default => '' -}; - -// Class calculation by type of Info Div -if(isset($arguments['autocomplete'])) { - $classes .= 'fields-people-autocomplete '; -} - - // If an attribute is frozen, inject a special link to unfreeze it, since // the attribute is read-only and the admin can't simply uncheck the setting if($fieldName == 'frozen' && $this->Field->getEntity()->frozen) { @@ -101,6 +79,6 @@ ?> -
  • +
  • element('form/fieldDiv')?>
  • diff --git a/app/templates/element/javascript.php b/app/templates/element/javascript.php index 7bbf30b89..23c171e21 100644 --- a/app/templates/element/javascript.php +++ b/app/templates/element/javascript.php @@ -175,7 +175,7 @@ $('#top-filters-submit').addClass("tss-rebalance"); } else { $('#top-filters-submit').removeClass("tss-rebalance"); - } + } } }); diff --git a/app/templates/element/peopleAutocomplete.php b/app/templates/element/peopleAutocomplete.php index bf7fbe1f4..73e690f97 100644 --- a/app/templates/element/peopleAutocomplete.php +++ b/app/templates/element/peopleAutocomplete.php @@ -27,7 +27,7 @@ // Get parameters $type = $type ?? 'stand-alone'; // autocomplete person picker type: 'stand-alone' or 'field', defaults to 'stand-alone'. - $label = $label ?? __d('operation','autocomplete.people.label'); + $label = $label ?? $formParams['label'] ?? __d('operation','autocomplete.people.label'); $fieldName = $fieldName ?? 'person_id'; $personType = $personType ?? 'coperson'; $htmlId = $htmlId ?? 'cmPersonPickerId'; @@ -38,6 +38,7 @@ $token = $this->request->getAttribute('csrfToken'); // Load my helper functions $vueHelper = $this->loadHelper('Vue'); + $inputValue = $inputValue ?? $formParams['value'] ?? ''; // If we have the $actionUrl array, construct the URL $constructedActionUrl = ''; @@ -80,7 +81,14 @@ personType: '', minLength: 2, // XXX probably should be set by config and default to 3 htmlId: '', - actionUrl: '' + actionUrl: '', + inputValue: '', + inputProps: { + name: '', + // This is not translated to data-personid but to datapersonid. + dataPersonid: '' + }, + formParams: , }, error: '' } diff --git a/app/webroot/js/comanage/components/autocomplete/cm-autocomplete-people.js b/app/webroot/js/comanage/components/autocomplete/cm-autocomplete-people.js index 6aabd382c..e5bb29cab 100644 --- a/app/webroot/js/comanage/components/autocomplete/cm-autocomplete-people.js +++ b/app/webroot/js/comanage/components/autocomplete/cm-autocomplete-people.js @@ -159,10 +159,12 @@ export default { }) }, setPerson() { - if(this.options.type == 'field') { + if(this.options.type == 'default') { + this.options.inputProps.dataPersonid = this.person.value + } else if(this.options.type == 'field') { // The picker is part of a standard form field const field = document.getElementById(this.options.fieldName); - field.value = this.person.value; + field.value = this.person.value; } else { // The picker is stand-alone, and should render the configured page in a modal on @item-select const urlForModal = this.options.actionUrl + '&person_id=' + this.person.value; @@ -180,6 +182,13 @@ export default { return str } }, + mounted() { + if(this.options.inputValue != undefined + && this.options.inputValue != '' + && this.options.htmlId == 'person_id') { + this.options.inputProps.value = `${this.options.formParams?.fullName} (ID: ${this.options.inputValue})` + } + }, computed: { hasMorePages: function() { if(this.rawData.responseMeta !== undefined) { @@ -206,17 +215,18 @@ export default {