Add support for bottom widgets in record view

- Allow configuring bottom widgets in record view
- Read from new entry in detailviewdefs
This commit is contained in:
Clemente Raposo 2021-12-28 18:04:13 +00:00 committed by c.raposo
parent b76a49b4a7
commit 4045d5ea25
11 changed files with 203 additions and 57 deletions

View file

@ -40,6 +40,7 @@ services:
$recordViewActions: '%module.recordview.actions%'
$recordViewActionLimits: '%module.recordview.actions_limits%'
$recordViewSidebarWidgets: '%module.recordview.sidebar_widgets%'
$recordViewBottomWidgets: '%module.recordview.bottom_widgets%'
$recordViewTopWidgets: '%module.recordview.top_widgets%'
$groupedFieldsTypeMap: '%record.fields.grouped_fields_type_map%'
$currencyFieldsTypeMap: '%record.fields.currency_fields_type_map%'

View file

@ -3,4 +3,8 @@ parameters:
default:
widgets: []
modules:
module.recordview.bottom_widgets:
default:
widgets: []
modules:

View file

@ -563,6 +563,7 @@ export * from './views/record/actions/save/record-save.action';
export * from './views/record/actions/save-new/record-save-new.action';
export * from './views/record/actions/toggle-widgets/record-widget-action.service';
export * from './views/record/adapters/actions.adapter';
export * from './views/record/adapters/bottom-widget.adapter';
export * from './views/record/adapters/record-content.adapter';
export * from './views/record/adapters/sidebar-widget.adapter';
export * from './views/record/adapters/top-widget.adapter';

View file

@ -52,6 +52,7 @@ export interface SummaryTemplates {
export interface RecordViewMetadata {
topWidget?: WidgetMetadata;
sidebarWidgets?: WidgetMetadata[];
bottomWidgets?: WidgetMetadata[];
actions?: Action[];
templateMeta?: RecordTemplateMetadata;
panels?: Panel[];
@ -450,6 +451,7 @@ export class MetadataStore implements StateStore {
panels: 'panels',
topWidget: 'topWidget',
sidebarWidgets: 'sidebarWidgets',
bottomWidgets: 'bottomWidgets',
summaryTemplates: 'summaryTemplates',
vardefs: 'vardefs'
};

View file

@ -0,0 +1,66 @@
/**
* SuiteCRM is a customer relationship management program developed by SalesAgility Ltd.
* Copyright (C) 2021 SalesAgility Ltd.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License version 3 as published by the
* Free Software Foundation with the addition of the following permission added
* to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
* IN WHICH THE COPYRIGHT IS OWNED BY SALESAGILITY, SALESAGILITY DISCLAIMS THE
* WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* In accordance with Section 7(b) of the GNU Affero General Public License
* version 3, these Appropriate Legal Notices must retain the display of the
* "Supercharged by SuiteCRM" logo. If the display of the logos is not reasonably
* feasible for technical reasons, the Appropriate Legal Notices must display
* the words "Supercharged by SuiteCRM".
*/
import {Injectable} from '@angular/core';
import {combineLatest} from 'rxjs';
import {map} from 'rxjs/operators';
import {MetadataStore} from '../../../store/metadata/metadata.store.service';
import {RecordViewStore} from '../store/record-view/record-view.store';
@Injectable()
export class BottomWidgetAdapter {
config$ = combineLatest([
this.metadata.recordViewMetadata$, this.store.widgets$
]).pipe(
map(([metadata, show]) => {
if (metadata.bottomWidgets && metadata.bottomWidgets.length) {
metadata.bottomWidgets.forEach(widget => {
if (widget && widget.refreshOn === 'data-update') {
widget.reload$ = this.store.record$.pipe(map(() => true));
}
if (widget) {
widget.subpanelReload$ = this.store.subpanelReload$;
}
});
}
return {
widgets: metadata.bottomWidgets || [],
show
};
})
);
constructor(
protected store: RecordViewStore,
protected metadata: MetadataStore
) {
}
}

View file

@ -65,6 +65,19 @@
</div>
</div>
<div *ngIf="vm.bottomWidgetConfig.show && vm.bottomWidgetConfig.widgets"
class="row no-gutters mt-4">
<div class="col">
<div *ngFor="let widget of vm.bottomWidgetConfig.widgets" class="mb-3">
<scrm-sidebar-widget [config]="widget"
[context$]="getViewContext$()"
[context]="getViewContext()"
[type]="widget.type">
</scrm-sidebar-widget>
</div>
</div>
</div>
<div *ngIf="vm.showSubpanels"
class="row no-gutters pt-1 pb-4">
<div class="col">

View file

@ -38,11 +38,12 @@ import {RecordViewStore} from '../../store/record-view/record-view.store';
import {RecordContentAdapter} from '../../adapters/record-content.adapter';
import {RecordContentDataSource} from '../../../../components/record-content/record-content.model';
import {TopWidgetAdapter} from '../../adapters/top-widget.adapter';
import {BottomWidgetAdapter} from '../../adapters/bottom-widget.adapter';
@Component({
selector: 'scrm-record-container',
templateUrl: 'record-container.component.html',
providers: [RecordContentAdapter, TopWidgetAdapter, SidebarWidgetAdapter]
providers: [RecordContentAdapter, TopWidgetAdapter, SidebarWidgetAdapter, BottomWidgetAdapter]
})
export class RecordContainerComponent implements OnInit, OnDestroy {
@ -50,13 +51,24 @@ export class RecordContainerComponent implements OnInit, OnDestroy {
language$: Observable<LanguageStrings> = this.language.vm$;
vm$ = combineLatest([
this.language$, this.sidebarWidgetAdapter.config$, this.topWidgetAdapter.config$, this.recordViewStore.showSubpanels$
this.language$,
this.sidebarWidgetAdapter.config$,
this.bottomWidgetAdapter.config$,
this.topWidgetAdapter.config$,
this.recordViewStore.showSubpanels$
]).pipe(
map((
[language, sidebarWidgetConfig, topWidgetConfig, showSubpanels]
[
language,
sidebarWidgetConfig,
bottomWidgetConfig,
topWidgetConfig,
showSubpanels
]
) => ({
language,
sidebarWidgetConfig,
bottomWidgetConfig,
topWidgetConfig,
showSubpanels
}))
@ -70,7 +82,8 @@ export class RecordContainerComponent implements OnInit, OnDestroy {
protected metadata: MetadataStore,
protected contentAdapter: RecordContentAdapter,
protected topWidgetAdapter: TopWidgetAdapter,
protected sidebarWidgetAdapter: SidebarWidgetAdapter
protected sidebarWidgetAdapter: SidebarWidgetAdapter,
protected bottomWidgetAdapter: BottomWidgetAdapter
) {
}

View file

@ -54,8 +54,6 @@ class RecordThreadDefinitionMapper implements ViewDefinitionMapperInterface
/**
* RecordThreadDefinitionMapper constructor.
* @param FieldDefinitionsProviderInterface $fieldDefinitionProvider
* @param FieldAliasMapper $fieldAliasMapper
*/
public function __construct(
FieldDefinitionsProviderInterface $fieldDefinitionProvider,
@ -66,7 +64,7 @@ class RecordThreadDefinitionMapper implements ViewDefinitionMapperInterface
}
/**
* @inheritDoc
* {@inheritDoc}
*/
public function getKey(): string
{
@ -74,7 +72,7 @@ class RecordThreadDefinitionMapper implements ViewDefinitionMapperInterface
}
/**
* @inheritDoc
* {@inheritDoc}
*/
public function getModule(): string
{
@ -82,60 +80,21 @@ class RecordThreadDefinitionMapper implements ViewDefinitionMapperInterface
}
/**
* @inheritDoc
* {@inheritDoc}
*/
public function map(ViewDefinition $definition, FieldDefinition $fieldDefinition): void
{
$recordView = $definition->getRecordView() ?? [];
$sidebarWidgets = $recordView['sidebarWidgets'] ?? [];
if (empty($recordView) || empty($sidebarWidgets)) {
return;
}
foreach ($sidebarWidgets as $widgetKey => $widget) {
$type = $widget['type'] ?? '';
if ($type !== 'record-thread') {
continue;
}
$options = $widget['options']['recordThread'] ?? [];
if (empty($options) || empty($options['module'])) {
continue;
}
$vardefs = $this->fieldDefinitionProvider->getVardef($options['module'])->getVardef();
if (empty($vardefs)) {
continue;
}
$this->addFieldDefinitions('item', 'header', $options, $vardefs);
$this->addFieldDefinitions('item', 'body', $options, $vardefs);
$this->addFieldDefinitions('create', 'header', $options, $vardefs);
$this->addFieldDefinitions('create', 'body', $options, $vardefs);
$widget['options']['recordThread'] = $options;
$recordView['sidebarWidgets'][$widgetKey] = $widget;
}
$this->mapWidgets($recordView, 'sidebarWidgets', $options, $vardefs);
$this->mapWidgets($recordView, 'bottomWidgets', $options, $vardefs);
$definition->setRecordView($recordView);
}
/**
* Add field definitions
* @param string $entryKey
* @param string $type
* @param array $options
* @param array $vardefs
* @return void
* Add field definitions.
*/
protected function addFieldDefinitions(string $entryKey, string $type, array &$options, array &$vardefs): void
{
@ -157,7 +116,6 @@ class RecordThreadDefinitionMapper implements ViewDefinitionMapperInterface
$cellDefinition = $col['field'];
}
if (!empty($col['displayParams'])) {
$cellDefinition['displayParams'] = $col['displayParams'];
@ -183,9 +141,9 @@ class RecordThreadDefinitionMapper implements ViewDefinitionMapperInterface
return $definition;
}
/**
* @param $definition
*
* @return mixed
*/
protected function mergeDisplayParams($definition)
@ -194,12 +152,12 @@ class RecordThreadDefinitionMapper implements ViewDefinitionMapperInterface
$toMerge = [
'required',
'readOnly',
'type'
'type',
];
foreach ($toMerge as $key) {
$attribute = $definition['displayParams'][$key] ?? null;
if ($attribute !== null) {
if (null !== $attribute) {
$fieldDefinitions[$key] = $attribute;
}
}
@ -210,9 +168,9 @@ class RecordThreadDefinitionMapper implements ViewDefinitionMapperInterface
}
/**
* Build list view column
* Build list view column.
*
* @param $definition
* @param array|null $vardefs
* @return array
*/
protected function buildFieldCell($definition, ?array &$vardefs): array
@ -225,4 +183,51 @@ class RecordThreadDefinitionMapper implements ViewDefinitionMapperInterface
$this->fieldAliasMapper
);
}
/**
* @param array|null $recordView
* @param string $widgetType
* @param $options
* @param $vardefs
*/
protected function mapWidgets(?array &$recordView, string $widgetType, &$options, &$vardefs): void
{
$widgets = $recordView[$widgetType] ?? [];
if (empty($recordView) || empty($widgets)) {
return;
}
foreach ($widgets as $widgetKey => $widget) {
$type = $widget['type'] ?? '';
if ('record-thread' !== $type) {
continue;
}
$options = $widget['options']['recordThread'] ?? [];
if (empty($options) || empty($options['module'])) {
continue;
}
$vardefs = $this->fieldDefinitionProvider->getVardef($options['module'])->getVardef();
if (empty($vardefs)) {
continue;
}
$this->addFieldDefinitions('item', 'header', $options, $vardefs);
$this->addFieldDefinitions('item', 'body', $options, $vardefs);
$this->addFieldDefinitions('create', 'header', $options, $vardefs);
$this->addFieldDefinitions('create', 'body', $options, $vardefs);
$widget['options']['recordThread'] = $options;
$recordView[$widgetType][$widgetKey] = $widget;
}
}
}

View file

@ -73,6 +73,11 @@ class RecordViewDefinitionHandler extends LegacyHandler
*/
private $recordViewSidebarWidgets;
/**
* @var array
*/
private $recordViewBottomWidgets;
/**
* @var array
*/
@ -95,6 +100,7 @@ class RecordViewDefinitionHandler extends LegacyHandler
* @param WidgetDefinitionProviderInterface $widgetDefinitionProvider
* @param FieldAliasMapper $fieldAliasMapper
* @param array $recordViewSidebarWidgets
* @param array $recordViewBottomWidgets
* @param array $recordViewTopWidgets
* @param SessionInterface $session
*/
@ -109,6 +115,7 @@ class RecordViewDefinitionHandler extends LegacyHandler
WidgetDefinitionProviderInterface $widgetDefinitionProvider,
FieldAliasMapper $fieldAliasMapper,
array $recordViewSidebarWidgets,
array $recordViewBottomWidgets,
array $recordViewTopWidgets,
SessionInterface $session
) {
@ -124,6 +131,7 @@ class RecordViewDefinitionHandler extends LegacyHandler
$this->actionDefinitionProvider = $actionDefinitionProvider;
$this->widgetDefinitionProvider = $widgetDefinitionProvider;
$this->recordViewSidebarWidgets = $recordViewSidebarWidgets;
$this->recordViewBottomWidgets = $recordViewBottomWidgets;
$this->recordViewTopWidgets = $recordViewTopWidgets;
$this->fieldAliasMapper = $fieldAliasMapper;
}
@ -177,6 +185,7 @@ class RecordViewDefinitionHandler extends LegacyHandler
'templateMeta' => [],
'topWidget' => [],
'sidebarWidgets' => [],
'bottomWidgets' => [],
'actions' => [],
'panels' => [],
'summaryTemplates' => [],
@ -186,6 +195,7 @@ class RecordViewDefinitionHandler extends LegacyHandler
$this->addTemplateMeta($detailViewDefs, $metadata);
$this->addTopWidgetConfig($module, $detailViewDefs, $metadata);
$this->addSidebarWidgetConfig($module, $detailViewDefs, $metadata);
$this->addBottomWidgetConfig($module, $detailViewDefs, $metadata);
$this->addPanelDefinitions($detailViewDefs, $editViewDefs, $vardefs, $metadata);
$this->addActionConfig($module, $detailViewDefs, $metadata);
$this->addSummaryTemplates($detailViewDefs, $metadata);
@ -347,6 +357,20 @@ class RecordViewDefinitionHandler extends LegacyHandler
);
}
/**
* @param string $module
* @param array $viewDefs
* @param array $metadata
*/
protected function addBottomWidgetConfig(string $module, array $viewDefs, array &$metadata): void
{
$metadata['bottomWidgets'] = $this->widgetDefinitionProvider->getBottomWidgets(
$this->recordViewBottomWidgets,
$module,
['widgets' => $viewDefs['bottomWidgets'] ?? []]
);
}
/**
* @param array $detailViewDefs
* @param array $editViewDefs

View file

@ -114,6 +114,14 @@ class WidgetDefinitionProvider extends LegacyHandler implements WidgetDefinition
return $this->parseEntries($config, $module, $moduleDefaults);
}
/**
* {@inheritDoc}
*/
public function getBottomWidgets(array $config, string $module, array $moduleDefaults = []): array
{
return $this->parseEntries($config, $module, $moduleDefaults);
}
/**
* @param array $config
* @param string $module

View file

@ -44,6 +44,15 @@ interface WidgetDefinitionProviderInterface
*/
public function getSidebarWidgets(array $config, string $module, array $moduleDefaults = []): array;
/**
* Get list of bottom widgets for module.
* @param array $config
* @param string $module
* @param array $moduleDefaults
* @return array
*/
public function getBottomWidgets(array $config, string $module, array $moduleDefaults = []): array;
/**
* Get list of top widgets for module
* @param array $config