mirror of
https://github.com/SuiteCRM/SuiteCRM-Core.git
synced 2025-09-02 08:09:19 +08:00
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:
parent
b76a49b4a7
commit
4045d5ea25
11 changed files with 203 additions and 57 deletions
|
@ -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%'
|
||||
|
|
|
@ -3,4 +3,8 @@ parameters:
|
|||
default:
|
||||
widgets: []
|
||||
modules:
|
||||
module.recordview.bottom_widgets:
|
||||
default:
|
||||
widgets: []
|
||||
modules:
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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'
|
||||
};
|
||||
|
|
|
@ -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
|
||||
) {
|
||||
}
|
||||
|
||||
}
|
|
@ -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">
|
||||
|
|
|
@ -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
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue