mirror of
https://github.com/SuiteCRM/SuiteCRM-Core.git
synced 2025-09-02 08:09:19 +08:00
Add standard action handling
- Make all actions consistent - Add base classes for action adapters and managers - Remove store dependency on table component - Make all actions consistent - Add base classes for action adapters and managers - Re-adjust existing and affected actions - Remove store dependency on table component - Add subpanel line actions adapter - Remove unlink action. Now all backend backend driven - Re-adjust line actions component -- Use action interface -- add line action limits configuration - Refactor subpanel line actions - Make table line actions optional
This commit is contained in:
parent
a68785147e
commit
453a0d3282
59 changed files with 1344 additions and 579 deletions
|
@ -27,6 +27,7 @@ services:
|
|||
$navigationTabLimits: '%themes.navigation_tab_limits%'
|
||||
$listViewBulkActions: '%module.listview.bulk_action%'
|
||||
$listViewLineActions: '%module.listview.line_action%'
|
||||
$listViewLineActionsLimits: '%module.listview.line_actions_limits%'
|
||||
$listViewSidebarWidgets: '%module.listview.sidebar_widgets%'
|
||||
$listViewColumnLimits: '%module.listview.column_limits%'
|
||||
$listViewSettingsLimits: '%module.listview.settings_limits%'
|
||||
|
|
|
@ -22,6 +22,7 @@ parameters:
|
|||
listview_column_limits: true
|
||||
listview_settings_limits: true
|
||||
listview_actions_limits: true
|
||||
listview_line_actions_limits: true
|
||||
module_routing: true
|
||||
recordview_actions_limits: true
|
||||
ui: true
|
||||
|
|
|
@ -6,29 +6,41 @@ parameters:
|
|||
key: create
|
||||
related_modules:
|
||||
calls:
|
||||
name : create-calls
|
||||
module : calls
|
||||
name: create-calls
|
||||
module: calls
|
||||
icon: phone
|
||||
labelKey: LBL_SCHEDULE_CALL
|
||||
returnAction: DetailView
|
||||
params:
|
||||
create:
|
||||
module: calls
|
||||
returnAction: DetailView
|
||||
meetings:
|
||||
name : create-meetings
|
||||
name: create-meetings
|
||||
module: meetings
|
||||
icon: calendar
|
||||
labelKey: LBL_SCHEDULE_MEETING
|
||||
returnAction: DetailView
|
||||
params:
|
||||
create:
|
||||
module: meetings
|
||||
returnAction: DetailView
|
||||
tasks:
|
||||
name : create-tasks
|
||||
name: create-tasks
|
||||
module: tasks
|
||||
icon: list
|
||||
labelKey: LBL_CREATE_TASK
|
||||
returnAction: DetailView
|
||||
params:
|
||||
create:
|
||||
module: tasks
|
||||
returnAction: DetailView
|
||||
emails:
|
||||
name : create-emails
|
||||
name: create-emails
|
||||
module: emails
|
||||
icon: email
|
||||
action: compose
|
||||
labelKey: LBL_COMPOSE_EMAIL_BUTTON_LABEL
|
||||
returnAction: index
|
||||
params:
|
||||
create:
|
||||
module: emails
|
||||
returnAction: index
|
||||
acl:
|
||||
- edit
|
||||
|
|
14
config/services/module/listview/line_actions_limits.yaml
Normal file
14
config/services/module/listview/line_actions_limits.yaml
Normal file
|
@ -0,0 +1,14 @@
|
|||
parameters:
|
||||
module.listview.line_actions_limits:
|
||||
without_sidebar:
|
||||
XSmall: 5
|
||||
Small: 5
|
||||
Medium: 5
|
||||
Large: 7
|
||||
XLarge: 8
|
||||
with_sidebar:
|
||||
XSmall: 5
|
||||
Small: 5
|
||||
Medium: 5
|
||||
Large: 6
|
||||
XLarge: 7
|
|
@ -25,19 +25,30 @@
|
|||
*/
|
||||
|
||||
import {Observable} from 'rxjs';
|
||||
import {ViewMode} from '../views/view.model';
|
||||
import {Record} from '../record/record.model';
|
||||
import {SearchCriteria} from '../views/list/search-criteria.model';
|
||||
|
||||
export interface ActionData {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface ActionHandlerMap {
|
||||
[key: string]: ActionHandler;
|
||||
export interface ActionHandlerMap<D extends ActionData> {
|
||||
[key: string]: ActionHandler<D>;
|
||||
}
|
||||
|
||||
export abstract class ActionHandler {
|
||||
export abstract class ActionHandler<D extends ActionData> {
|
||||
abstract key: string;
|
||||
|
||||
abstract run(data: ActionData): void;
|
||||
abstract modes: ViewMode[];
|
||||
|
||||
abstract run(data: D, action?: Action): void;
|
||||
|
||||
getStatus(data: D): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
abstract shouldDisplay(data: D): boolean;
|
||||
}
|
||||
|
||||
export interface ModeActions {
|
||||
|
@ -58,7 +69,23 @@ export interface Action {
|
|||
}
|
||||
|
||||
export interface ActionDataSource {
|
||||
getActions(): Observable<Action[]>;
|
||||
getActions(context?: ActionContext): Observable<Action[]>;
|
||||
|
||||
runAction(action: Action): void;
|
||||
runAction(action: Action, context?: ActionContext): void;
|
||||
}
|
||||
|
||||
export interface ActionManager<D extends ActionData> {
|
||||
|
||||
run(action: Action, mode: ViewMode, data: D): void;
|
||||
|
||||
getHandler(action: Action, mode: ViewMode): ActionHandler<D>;
|
||||
}
|
||||
|
||||
export interface ActionContext {
|
||||
[key: string]: any;
|
||||
|
||||
module?: string;
|
||||
record?: Record;
|
||||
ids?: string[];
|
||||
criteria?: SearchCriteria;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
export * from './actions/action.model';
|
||||
export * from './actions/bulk-action.model';
|
||||
export * from './actions/line-action.model';
|
||||
export * from './components/button/button-group.model';
|
||||
export * from './components/button/button.model';
|
||||
export * from './components/button/dropdown-button.model';
|
||||
|
|
|
@ -26,15 +26,15 @@
|
|||
|
||||
import {ViewFieldDefinition} from './metadata.model';
|
||||
import {WidgetMetadata} from './widget.metadata';
|
||||
import {LineAction} from '../actions/line-action.model';
|
||||
import {FieldDefinition} from '../record/field.model';
|
||||
import {BulkActionsMap} from '../actions/bulk-action.model';
|
||||
import {ChartTypesMap} from '../containers/chart/chart.model';
|
||||
import {Action} from '../actions/action.model';
|
||||
|
||||
export interface RecordListMeta {
|
||||
fields: ColumnDefinition[];
|
||||
bulkActions: BulkActionsMap;
|
||||
lineActions: LineAction[];
|
||||
lineActions: Action[];
|
||||
filters: Filter[];
|
||||
}
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
import {ColumnDefinition} from './list.metadata.model';
|
||||
import {WidgetOptionMap} from './widget.metadata';
|
||||
import {LineAction} from "../actions/line-action.model";
|
||||
import {Action} from '../actions/action.model';
|
||||
|
||||
export interface SubPanelTopButton {
|
||||
key: string;
|
||||
|
@ -69,7 +69,7 @@ export interface SubPanelDefinition {
|
|||
collection_list: SubPanelCollectionList;
|
||||
columns: ColumnDefinition[];
|
||||
icon?: string;
|
||||
lineActions?: LineAction[];
|
||||
lineActions?: Action[];
|
||||
get_subpanel_data?: string;
|
||||
}
|
||||
|
||||
|
|
|
@ -55,8 +55,7 @@ import {AppStateStore} from '../../store/app-state/app-state.store';
|
|||
import {recordActionsMock} from '../../views/record/adapters/actions.adapter.spec.mock';
|
||||
import {RecordActionsAdapter} from '../../views/record/adapters/actions.adapter';
|
||||
import {ImageModule} from '../image/image.module';
|
||||
import {ActionDataSource} from 'common';
|
||||
import {Action} from '../../../../../common/src/lib/actions/action.model';
|
||||
import {ActionDataSource, Action} from 'common';
|
||||
|
||||
@Component({
|
||||
selector: 'action-group-menu-test-host-component',
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
import {Component, Input, OnInit} from '@angular/core';
|
||||
import {BehaviorSubject, combineLatest, Observable, Subscription} from 'rxjs';
|
||||
import {map} from 'rxjs/operators';
|
||||
import {Action, ActionDataSource, Button, ButtonGroupInterface, ButtonInterface} from 'common';
|
||||
import {Action, ActionContext, ActionDataSource, Button, ButtonGroupInterface, ButtonInterface} from 'common';
|
||||
import {SystemConfigStore} from '../../store/system-config/system-config.store';
|
||||
import {
|
||||
ScreenSize,
|
||||
|
@ -49,6 +49,7 @@ export class ActionGroupMenuComponent implements OnInit {
|
|||
|
||||
@Input() klass = '';
|
||||
@Input() buttonClass = 'btn btn-sm';
|
||||
@Input() actionContext: ActionContext;
|
||||
@Input() config: ActionDataSource;
|
||||
configState = new BehaviorSubject<ButtonGroupInterface>({buttons: []});
|
||||
config$ = this.configState.asObservable();
|
||||
|
@ -146,7 +147,7 @@ export class ActionGroupMenuComponent implements OnInit {
|
|||
label: action.label || '',
|
||||
klass: this.buttonClass,
|
||||
onClick: (): void => {
|
||||
this.config.runAction(action);
|
||||
this.config.runAction(action, this.actionContext);
|
||||
}
|
||||
} as ButtonInterface;
|
||||
|
||||
|
|
|
@ -25,25 +25,11 @@
|
|||
* the words "Supercharged by SuiteCRM".
|
||||
*/
|
||||
-->
|
||||
<div id="line-action-div" class="line-action float-right"
|
||||
>
|
||||
<div
|
||||
*ngFor="let item of items"
|
||||
class="line-action-item line-action float-right"
|
||||
placement="left" ngbTooltip="{{ item.label }}"
|
||||
>
|
||||
<a *ngIf="item.routing === false"
|
||||
[ngClass]="item.key"
|
||||
(click)="runAction(item.action)">
|
||||
<scrm-image image="{{ item.icon }}"></scrm-image>
|
||||
</a>
|
||||
|
||||
<a *ngIf="item.routing !== false"
|
||||
[ngClass]="item.key"
|
||||
[routerLink]="item.link.route"
|
||||
[queryParams]="item.link.params">
|
||||
<scrm-image image="{{ item.icon }}"></scrm-image>
|
||||
</a>
|
||||
|
||||
<ng-container *ngIf="(vm$ | async) as vm">
|
||||
<div id="line-action-div" class="line-action float-right">
|
||||
<div class="{{klass}}">
|
||||
<scrm-button-group *ngIf="config$" [config$]="config$"></scrm-button-group>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
|
|
|
@ -25,82 +25,144 @@
|
|||
*/
|
||||
|
||||
import {Component, Input, OnInit} from '@angular/core';
|
||||
import {LineAction, Record} from 'common';
|
||||
import {LanguageStore} from '../../store/language/language.store';
|
||||
import {Action, ActionContext, ActionDataSource, Button, ButtonGroupInterface, ButtonInterface, Record} from 'common';
|
||||
import {LanguageStore, LanguageStrings} from '../../store/language/language.store';
|
||||
import {SubpanelActionManager} from "../../containers/subpanel/components/subpanel/action-manager.service";
|
||||
import {SubpanelActionData} from "../../containers/subpanel/actions/subpanel.action";
|
||||
import {SubpanelStore} from "../../containers/subpanel/store/subpanel/subpanel.store";
|
||||
import {BehaviorSubject, combineLatest, Observable, Subscription} from 'rxjs';
|
||||
import {
|
||||
ScreenSize,
|
||||
ScreenSizeObserverService
|
||||
} from '../../services/ui/screen-size-observer/screen-size-observer.service';
|
||||
import {SystemConfigStore} from '../../store/system-config/system-config.store';
|
||||
import {map} from 'rxjs/operators';
|
||||
|
||||
export interface LineActionMenuViewModel {
|
||||
actions: Action[];
|
||||
screenSize: ScreenSize;
|
||||
languages: LanguageStrings;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'scrm-line-action-menu',
|
||||
templateUrl: 'line-action-menu.component.html'
|
||||
})
|
||||
|
||||
export class LineActionMenuComponent implements OnInit {
|
||||
|
||||
@Input() lineActions: LineAction[];
|
||||
@Input() klass = '';
|
||||
@Input() record: Record;
|
||||
@Input() store: SubpanelStore;
|
||||
@Input() config: ActionDataSource;
|
||||
@Input() limitConfigKey = 'listview_line_actions_limits';
|
||||
configState = new BehaviorSubject<ButtonGroupInterface>({buttons: []});
|
||||
config$ = this.configState.asObservable();
|
||||
|
||||
items: LineAction[];
|
||||
vm$: Observable<LineActionMenuViewModel>;
|
||||
|
||||
constructor(protected languageStore: LanguageStore,
|
||||
protected actionManager: SubpanelActionManager
|
||||
protected buttonClass = 'line-action-item line-action';
|
||||
protected buttonGroupClass = 'float-right';
|
||||
|
||||
protected subs: Subscription[];
|
||||
protected screen: ScreenSize = ScreenSize.Medium;
|
||||
protected defaultBreakpoint = 3;
|
||||
protected breakpoint: number;
|
||||
|
||||
|
||||
constructor(
|
||||
protected languageStore: LanguageStore,
|
||||
protected actionManager: SubpanelActionManager,
|
||||
protected languages: LanguageStore,
|
||||
protected screenSize: ScreenSizeObserverService,
|
||||
protected systemConfigStore: SystemConfigStore,
|
||||
) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.setLineActions();
|
||||
this.vm$ = combineLatest([
|
||||
this.config.getActions(this.record),
|
||||
this.screenSize.screenSize$,
|
||||
this.languages.vm$
|
||||
]).pipe(
|
||||
map(([actions, screenSize, languages]) => {
|
||||
if (screenSize) {
|
||||
this.screen = screenSize;
|
||||
}
|
||||
this.configState.next(this.getButtonGroupConfig(actions));
|
||||
|
||||
return {actions, screenSize, languages};
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
setLineActions(): void {
|
||||
const actions = [];
|
||||
getButtonGroupConfig(actions: Action[]): ButtonGroupInterface {
|
||||
|
||||
this.lineActions.forEach(action => {
|
||||
const recordAction = {...action};
|
||||
const expanded = [];
|
||||
const collapsed = [];
|
||||
|
||||
const routing = action.routing ?? '';
|
||||
actions.forEach((action: Action) => {
|
||||
const button = this.buildButton(action);
|
||||
|
||||
recordAction.label = this.languageStore.getAppString(recordAction.labelKey);
|
||||
|
||||
if (routing !== false) {
|
||||
|
||||
const params: { [key: string]: any } = {};
|
||||
/* eslint-disable camelcase,@typescript-eslint/camelcase*/
|
||||
params.return_module = action.legacyModuleName;
|
||||
params.return_action = action.returnAction;
|
||||
params.return_id = this.record.id;
|
||||
/* eslint-enable camelcase,@typescript-eslint/camelcase */
|
||||
params[action.mapping.moduleName] = action.legacyModuleName;
|
||||
|
||||
params[action.mapping.name] = this.record.attributes.name;
|
||||
params[action.mapping.id] = this.record.id;
|
||||
|
||||
recordAction.link = {
|
||||
label: recordAction.label,
|
||||
url: null,
|
||||
route: '/' + action.module + '/' + action.action,
|
||||
params
|
||||
};
|
||||
if (action.params && action.params.expanded) {
|
||||
expanded.push(button);
|
||||
return;
|
||||
}
|
||||
|
||||
actions.push(recordAction);
|
||||
collapsed.push(button);
|
||||
});
|
||||
this.items = actions.reverse();
|
||||
|
||||
let breakpoint = this.getBreakpoint();
|
||||
if (expanded.length < breakpoint) {
|
||||
breakpoint = expanded.length;
|
||||
}
|
||||
|
||||
const buttons = expanded.concat(collapsed);
|
||||
|
||||
return {
|
||||
buttonKlass: [this.buttonClass],
|
||||
dropdownLabel: this.languages.getAppString('LBL_ACTIONS') || '',
|
||||
breakpoint,
|
||||
dropdownOptions: {
|
||||
placement: ['bottom-right'],
|
||||
wrapperKlass: [(this.buttonGroupClass)]
|
||||
},
|
||||
buttons
|
||||
} as ButtonGroupInterface;
|
||||
}
|
||||
|
||||
runAction(actionKey: string) {
|
||||
getBreakpoint(): number {
|
||||
const breakpointMap = this.systemConfigStore.getConfigValue(this.limitConfigKey);
|
||||
|
||||
const subpanelActionData = {
|
||||
subpanelMeta: this.store.metadata,
|
||||
module: this.record.module || this.store.metadata.module,
|
||||
id: this.record.id,
|
||||
parentModule: this.store.parentModule,
|
||||
parentId: this.store.parentId,
|
||||
store: this.store
|
||||
} as SubpanelActionData;
|
||||
if (this.screen && breakpointMap && breakpointMap[this.screen]) {
|
||||
this.breakpoint = breakpointMap[this.screen];
|
||||
return this.breakpoint;
|
||||
}
|
||||
|
||||
this.actionManager.run(actionKey, subpanelActionData);
|
||||
if (this.breakpoint) {
|
||||
return this.breakpoint;
|
||||
}
|
||||
|
||||
return this.defaultBreakpoint;
|
||||
}
|
||||
|
||||
protected buildButton(action: Action): ButtonInterface {
|
||||
const button = {
|
||||
titleKey: action.labelKey || '',
|
||||
klass: this.buttonClass,
|
||||
icon: action.icon || '',
|
||||
onClick: (): void => {
|
||||
this.config.runAction(action, {
|
||||
module: (this.record && this.record.module) || '',
|
||||
record: this.record
|
||||
} as ActionContext);
|
||||
}
|
||||
} as ButtonInterface;
|
||||
|
||||
if (action.icon) {
|
||||
button.icon = action.icon;
|
||||
}
|
||||
|
||||
if (action.status) {
|
||||
Button.appendClasses(button, [action.status]);
|
||||
}
|
||||
|
||||
return button;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ import {LineActionMenuComponent} from './line-action-menu.component';
|
|||
import {NgbModule} from '@ng-bootstrap/ng-bootstrap';
|
||||
import {RouterModule} from '@angular/router';
|
||||
import {ImageModule} from '../image/image.module';
|
||||
import {ButtonGroupModule} from '../button-group/button-group.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [LineActionMenuComponent],
|
||||
|
@ -40,6 +41,7 @@ import {ImageModule} from '../image/image.module';
|
|||
NgbModule,
|
||||
ImageModule,
|
||||
RouterModule,
|
||||
ButtonGroupModule,
|
||||
]
|
||||
})
|
||||
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
/**
|
||||
* 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 {Action, ActionContext} from 'common';
|
||||
import {AsyncActionService} from '../../../services/process/processes/async-action/async-action';
|
||||
import {MessageService} from '../../../services/message/message.service';
|
||||
import {LineActionActionManager} from '../line-actions/line-action-manager.service';
|
||||
import {LineActionData} from '../line-actions/line.action';
|
||||
import {ConfirmationModalService} from '../../../services/modals/confirmation-modal.service';
|
||||
import {BaseRecordActionsAdapter} from '../../../services/actions/base-record-action.adapter';
|
||||
import {LanguageStore} from '../../../store/language/language.store';
|
||||
|
||||
@Injectable()
|
||||
export abstract class BaseLineActionsAdapter extends BaseRecordActionsAdapter<LineActionData> {
|
||||
|
||||
protected constructor(
|
||||
protected actionManager: LineActionActionManager,
|
||||
protected asyncActionService: AsyncActionService,
|
||||
protected message: MessageService,
|
||||
protected confirmation: ConfirmationModalService,
|
||||
protected language: LanguageStore
|
||||
) {
|
||||
super(actionManager, asyncActionService, message, confirmation, language)
|
||||
}
|
||||
|
||||
protected buildActionData(action: Action, context?: ActionContext): LineActionData {
|
||||
return {
|
||||
record: (context && context.record) || null
|
||||
} as LineActionData;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
/**
|
||||
* 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 {Router} from '@angular/router';
|
||||
import {Action, ActionHandler, ViewMode} from 'common';
|
||||
import {ModuleNameMapper} from '../../../../services/navigation/module-name-mapper/module-name-mapper.service';
|
||||
import {LineActionData} from '../line.action';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class CreateRelatedLineAction extends ActionHandler<LineActionData> {
|
||||
key = 'create';
|
||||
modes = ['list' as ViewMode];
|
||||
|
||||
constructor(protected moduleNameMapper: ModuleNameMapper, protected router: Router) {
|
||||
super();
|
||||
}
|
||||
|
||||
run(data: LineActionData, action: Action = null): void {
|
||||
|
||||
const configs = action.params['create'] || {} as any;
|
||||
|
||||
const params: { [key: string]: any } = {};
|
||||
/* eslint-disable camelcase,@typescript-eslint/camelcase*/
|
||||
params.return_module = configs.legacyModuleName;
|
||||
params.return_action = configs.returnAction;
|
||||
params.return_id = data.record.id;
|
||||
/* eslint-enable camelcase,@typescript-eslint/camelcase */
|
||||
params[configs.mapping.moduleName] = configs.legacyModuleName;
|
||||
|
||||
params[configs.mapping.name] = data.record.attributes.name;
|
||||
params[configs.mapping.id] = data.record.id;
|
||||
|
||||
const route = '/' + configs.module + '/' + configs.action;
|
||||
|
||||
this.router.navigate([route], {
|
||||
queryParams: params
|
||||
}).then();
|
||||
}
|
||||
|
||||
shouldDisplay(data: LineActionData): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/**
|
||||
* 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 {LineActionData} from './line.action';
|
||||
import {CreateRelatedLineAction} from './create-related/create-related.action';
|
||||
import {BaseActionManager} from '../../../services/actions/base-action-manager.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class LineActionActionManager extends BaseActionManager<LineActionData> {
|
||||
|
||||
constructor(
|
||||
protected createRelated: CreateRelatedLineAction,
|
||||
) {
|
||||
super();
|
||||
createRelated.modes.forEach(mode => this.actions[mode][createRelated.key] = createRelated);
|
||||
}
|
||||
}
|
|
@ -24,20 +24,9 @@
|
|||
* the words "Supercharged by SuiteCRM".
|
||||
*/
|
||||
|
||||
import {MenuItemLink} from '../menu/menu.model';
|
||||
import {ActionData, Record} from 'common';
|
||||
|
||||
export interface LineAction {
|
||||
key: string;
|
||||
labelKey: string;
|
||||
label: string;
|
||||
module: string;
|
||||
legacyModuleName: string;
|
||||
icon: string;
|
||||
action: string;
|
||||
returnAction: string;
|
||||
params: { [key: string]: any };
|
||||
mapping: { [key: string]: any };
|
||||
link: MenuItemLink;
|
||||
acl: string[];
|
||||
routing?: boolean;
|
||||
export interface LineActionData extends ActionData {
|
||||
record: Record;
|
||||
}
|
||||
|
|
@ -77,7 +77,10 @@
|
|||
<th cdk-header-cell scope="col" *cdkHeaderCellDef class="primary-table-header"></th>
|
||||
|
||||
<td cdk-cell *cdkCellDef="let record">
|
||||
<scrm-line-action-menu [lineActions]="vm.lineActions" [record]="record" [store]="store"></scrm-line-action-menu>
|
||||
<scrm-line-action-menu *ngIf="record && config.lineActions && config.lineActions.getActions()"
|
||||
[config]="config.lineActions"
|
||||
[record]="record">
|
||||
</scrm-line-action-menu>
|
||||
</td>
|
||||
|
||||
</ng-container>
|
||||
|
|
|
@ -28,9 +28,9 @@ import {Component, Input, OnInit} from '@angular/core';
|
|||
import {combineLatest, Observable, of} from 'rxjs';
|
||||
import {map, shareReplay} from 'rxjs/operators';
|
||||
import {
|
||||
Action,
|
||||
ColumnDefinition,
|
||||
Field,
|
||||
LineAction,
|
||||
Record,
|
||||
RecordSelection,
|
||||
SelectionStatus,
|
||||
|
@ -40,11 +40,10 @@ import {
|
|||
import {FieldManager} from '../../../services/record/field/field.manager';
|
||||
import {TableConfig} from '../table.model';
|
||||
import {SortDirectionDataSource} from '../../sort-button/sort-button.model';
|
||||
import {SubpanelStore} from "../../../containers/subpanel/store/subpanel/subpanel.store";
|
||||
|
||||
interface TableViewModel {
|
||||
columns: ColumnDefinition[];
|
||||
lineActions: LineAction[];
|
||||
lineActions: Action[];
|
||||
selection: RecordSelection;
|
||||
selected: { [key: string]: string };
|
||||
selectionStatus: SelectionStatus;
|
||||
|
@ -59,7 +58,6 @@ interface TableViewModel {
|
|||
})
|
||||
export class TableBodyComponent implements OnInit {
|
||||
@Input() config: TableConfig;
|
||||
@Input() store: SubpanelStore;
|
||||
maxColumns = 4;
|
||||
vm$: Observable<TableViewModel>;
|
||||
|
||||
|
@ -69,13 +67,13 @@ export class TableBodyComponent implements OnInit {
|
|||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
const lineAction$ = this.config.lineActions$ || of([]).pipe(shareReplay(1));
|
||||
const selection$ = this.config.selection$ || of(null).pipe(shareReplay(1));
|
||||
const loading$ = this.config.loading$ || of(false).pipe(shareReplay(1));
|
||||
const lineActions$ = (this.config.lineActions && this.config.lineActions.getActions()) || of([]).pipe(shareReplay(1));
|
||||
|
||||
this.vm$ = combineLatest([
|
||||
this.config.columns,
|
||||
lineAction$,
|
||||
lineActions$,
|
||||
selection$,
|
||||
this.config.maxColumns$,
|
||||
this.config.dataSource.connect(null),
|
||||
|
@ -136,15 +134,15 @@ export class TableBodyComponent implements OnInit {
|
|||
let hasLinkField = false;
|
||||
const returnArray = [];
|
||||
|
||||
const fields = metaFields.filter(function(field){
|
||||
const fields = metaFields.filter(function (field) {
|
||||
return !field.hasOwnProperty('default')
|
||||
|| (field.hasOwnProperty('default') && field.default === true);
|
||||
});
|
||||
|
||||
while (i < this.maxColumns && i < fields.length) {
|
||||
returnArray.push(fields[i].name);
|
||||
hasLinkField = hasLinkField || fields[i].link;
|
||||
i++;
|
||||
returnArray.push(fields[i].name);
|
||||
hasLinkField = hasLinkField || fields[i].link;
|
||||
i++;
|
||||
}
|
||||
if (!hasLinkField && (this.maxColumns < fields.length)) {
|
||||
for (i = this.maxColumns; i < fields.length; i++) {
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
[pagination]="config.pagination">
|
||||
</scrm-table-header>
|
||||
|
||||
<scrm-table-body [config]="config" [store]="store"></scrm-table-body>
|
||||
<scrm-table-body [config]="config"></scrm-table-body>
|
||||
|
||||
<scrm-table-footer *ngIf="showFooter()"
|
||||
[selection]="config.selection"
|
||||
|
|
|
@ -26,7 +26,6 @@
|
|||
|
||||
import {Component, Input} from '@angular/core';
|
||||
import {TableConfig} from './table.model';
|
||||
import {SubpanelStore} from "../../containers/subpanel/store/subpanel/subpanel.store";
|
||||
|
||||
@Component({
|
||||
selector: 'scrm-table',
|
||||
|
@ -35,7 +34,6 @@ import {SubpanelStore} from "../../containers/subpanel/store/subpanel/subpanel.s
|
|||
})
|
||||
export class TableComponent {
|
||||
@Input() config: TableConfig;
|
||||
@Input() store: SubpanelStore;
|
||||
|
||||
showHeader(): boolean {
|
||||
return this.config.showHeader;
|
||||
|
|
|
@ -27,8 +27,8 @@
|
|||
import {Observable} from 'rxjs';
|
||||
import {DataSource} from '@angular/cdk/collections';
|
||||
import {
|
||||
ActionDataSource,
|
||||
ColumnDefinition,
|
||||
LineAction,
|
||||
PaginationDataSource,
|
||||
Record,
|
||||
RecordSelection,
|
||||
|
@ -46,7 +46,7 @@ export interface TableConfig {
|
|||
|
||||
columns: Observable<ColumnDefinition[]>;
|
||||
maxColumns$: Observable<number>;
|
||||
lineActions$?: Observable<LineAction[]>;
|
||||
lineActions?: ActionDataSource;
|
||||
selection$?: Observable<RecordSelection>;
|
||||
sort$?: Observable<SortingSelection>;
|
||||
loading$?: Observable<boolean>;
|
||||
|
|
|
@ -30,7 +30,6 @@ import {take} from 'rxjs/operators';
|
|||
import {MessageService} from '../../../../services/message/message.service';
|
||||
import {SavedFilterActionData, SavedFilterActionHandler} from '../saved-filter.action';
|
||||
import {AsyncActionInput, AsyncActionService} from '../../../../services/process/processes/async-action/async-action';
|
||||
import {SavedFilterStore} from '../../store/saved-filter/saved-filter.store';
|
||||
import {SavedFilter} from '../../../../store/saved-filters/saved-filter.model';
|
||||
|
||||
@Injectable({
|
||||
|
@ -74,8 +73,8 @@ export class SavedFilterDeleteAction extends SavedFilterActionHandler {
|
|||
});
|
||||
}
|
||||
|
||||
shouldDisplay(store: SavedFilterStore): boolean {
|
||||
|
||||
shouldDisplay(data: SavedFilterActionData): boolean {
|
||||
const store = data && data.store;
|
||||
const filter = (store && store.recordStore.getBaseRecord()) || {} as SavedFilter;
|
||||
|
||||
return !!filter.id;
|
||||
|
|
|
@ -25,42 +25,22 @@
|
|||
*/
|
||||
|
||||
import {Injectable} from '@angular/core';
|
||||
import {ViewMode} from 'common';
|
||||
import {SavedFilterActionData, SavedFilterActionHandler, SavedFilterActionHandlerMap} from './saved-filter.action';
|
||||
import {SavedFilterActionData} from './saved-filter.action';
|
||||
import {SavedFilterSaveAction} from './save/saved-filter-save.action';
|
||||
import {SavedFilterDeleteAction} from './delete/saved-filter-delete.action';
|
||||
import {BaseActionManager} from '../../../services/actions/base-action-manager.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class SavedFilterActionManager {
|
||||
|
||||
actions: { [key: string]: SavedFilterActionHandlerMap } = {
|
||||
edit: {} as SavedFilterActionHandlerMap,
|
||||
detail: {} as SavedFilterActionHandlerMap,
|
||||
};
|
||||
export class SavedFilterActionManager extends BaseActionManager<SavedFilterActionData> {
|
||||
|
||||
constructor(
|
||||
save: SavedFilterSaveAction,
|
||||
deleteAction: SavedFilterDeleteAction
|
||||
) {
|
||||
super();
|
||||
save.modes.forEach(mode => this.actions[mode][save.key] = save);
|
||||
deleteAction.modes.forEach(mode => this.actions[mode][deleteAction.key] = deleteAction);
|
||||
}
|
||||
|
||||
run(actionKey: string, mode: ViewMode, data: SavedFilterActionData): void {
|
||||
if (!this.actions || !this.actions[mode] || !this.actions[mode][actionKey]) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.actions[mode][actionKey].run(data);
|
||||
}
|
||||
|
||||
getHandler(action: string, mode: ViewMode): SavedFilterActionHandler {
|
||||
if (!this.actions || !this.actions[mode] || !this.actions[mode][action]) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.actions[mode][action];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
* the words "Supercharged by SuiteCRM".
|
||||
*/
|
||||
|
||||
import {ActionData, ActionHandler, ViewMode} from 'common';
|
||||
import {ActionData, ActionHandler} from 'common';
|
||||
import {SavedFilterStore} from '../store/saved-filter/saved-filter.store';
|
||||
import {ListFilterStore} from '../store/list-filter/list-filter.store';
|
||||
|
||||
|
@ -33,19 +33,9 @@ export interface SavedFilterActionData extends ActionData {
|
|||
listFilterStore: ListFilterStore;
|
||||
}
|
||||
|
||||
export interface SavedFilterActionHandlerMap {
|
||||
[key: string]: SavedFilterActionHandler;
|
||||
}
|
||||
|
||||
export abstract class SavedFilterActionHandler extends ActionHandler {
|
||||
|
||||
abstract modes: ViewMode[];
|
||||
|
||||
getStatus(store: SavedFilterStore): string {
|
||||
return '';
|
||||
}
|
||||
export abstract class SavedFilterActionHandler extends ActionHandler<SavedFilterActionData> {
|
||||
|
||||
abstract run(data: SavedFilterActionData): void;
|
||||
|
||||
abstract shouldDisplay(store: SavedFilterStore): boolean;
|
||||
abstract shouldDisplay(data: SavedFilterActionData): boolean;
|
||||
}
|
||||
|
|
|
@ -25,10 +25,10 @@
|
|||
*/
|
||||
|
||||
import {Injectable} from '@angular/core';
|
||||
import {Action, ActionDataSource, ModeActions} from 'common';
|
||||
import {Action, ActionContext, ViewMode} from 'common';
|
||||
import {combineLatest, Observable} from 'rxjs';
|
||||
import {map, take} from 'rxjs/operators';
|
||||
import {AsyncActionInput, AsyncActionService} from '../../../services/process/processes/async-action/async-action';
|
||||
import {AsyncActionService} from '../../../services/process/processes/async-action/async-action';
|
||||
import {LanguageStore} from '../../../store/language/language.store';
|
||||
import {MessageService} from '../../../services/message/message.service';
|
||||
import {Process} from '../../../services/process/process.service';
|
||||
|
@ -37,15 +37,10 @@ import {SavedFilterActionManager} from '../actions/saved-filter-action-manager.s
|
|||
import {SavedFilterActionData} from '../actions/saved-filter.action';
|
||||
import {ListFilterStore} from '../store/list-filter/list-filter.store';
|
||||
import {ConfirmationModalService} from '../../../services/modals/confirmation-modal.service';
|
||||
import {BaseRecordActionsAdapter} from '../../../services/actions/base-record-action.adapter';
|
||||
|
||||
@Injectable()
|
||||
export class SavedFilterActionsAdapter implements ActionDataSource {
|
||||
|
||||
defaultActions: ModeActions = {
|
||||
detail: [],
|
||||
edit: [],
|
||||
create: [],
|
||||
};
|
||||
export class SavedFilterActionsAdapter extends BaseRecordActionsAdapter<SavedFilterActionData> {
|
||||
|
||||
constructor(
|
||||
protected store: SavedFilterStore,
|
||||
|
@ -54,11 +49,18 @@ export class SavedFilterActionsAdapter implements ActionDataSource {
|
|||
protected actionManager: SavedFilterActionManager,
|
||||
protected asyncActionService: AsyncActionService,
|
||||
protected message: MessageService,
|
||||
protected confimation: ConfirmationModalService
|
||||
protected confirmation: ConfirmationModalService,
|
||||
) {
|
||||
super(
|
||||
actionManager,
|
||||
asyncActionService,
|
||||
message,
|
||||
confirmation,
|
||||
language
|
||||
)
|
||||
}
|
||||
|
||||
getActions(): Observable<Action[]> {
|
||||
getActions(context?: ActionContext): Observable<Action[]> {
|
||||
return combineLatest(
|
||||
[
|
||||
this.store.meta$,
|
||||
|
@ -70,113 +72,35 @@ export class SavedFilterActionsAdapter implements ActionDataSource {
|
|||
map((
|
||||
[
|
||||
meta,
|
||||
mode,
|
||||
record,
|
||||
languages,
|
||||
mode
|
||||
]
|
||||
) => {
|
||||
if (!mode || !meta) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const availableActions = {
|
||||
detail: [],
|
||||
edit: [],
|
||||
} as ModeActions;
|
||||
|
||||
if (meta.actions && meta.actions.length) {
|
||||
meta.actions.forEach(action => {
|
||||
if (!action.modes || !action.modes.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
action.modes.forEach(actionMode => {
|
||||
if (!availableActions[actionMode]) {
|
||||
return;
|
||||
}
|
||||
availableActions[actionMode].push(action);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
availableActions.detail = availableActions.detail.concat(this.defaultActions.detail);
|
||||
availableActions.edit = availableActions.edit.concat(this.defaultActions.edit);
|
||||
|
||||
const actions = [];
|
||||
|
||||
availableActions[mode].forEach(action => {
|
||||
|
||||
if (!action.asyncProcess) {
|
||||
const actionHandler = this.actionManager.getHandler(action.key, mode);
|
||||
|
||||
if (!actionHandler || !actionHandler.shouldDisplay(this.store)) {
|
||||
return;
|
||||
}
|
||||
action.status = actionHandler.getStatus(this.store) || '';
|
||||
}
|
||||
|
||||
const label = this.language.getFieldLabel(action.labelKey, record.module, languages);
|
||||
actions.push({
|
||||
...action,
|
||||
label
|
||||
});
|
||||
});
|
||||
|
||||
return actions;
|
||||
return this.parseModeActions(meta.actions, mode);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
runAction(action: Action): void {
|
||||
const params = (action && action.params) || {} as { [key: string]: any };
|
||||
const displayConfirmation = params.displayConfirmation || false;
|
||||
const confirmationLabel = params.confirmationLabel || '';
|
||||
|
||||
if (displayConfirmation) {
|
||||
this.confimation.showModal(confirmationLabel, () => {
|
||||
this.callAction(action);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.callAction(action);
|
||||
}
|
||||
|
||||
protected callAction(action: Action) {
|
||||
if (action.asyncProcess) {
|
||||
this.runAsyncAction(action);
|
||||
return;
|
||||
}
|
||||
this.runFrontEndAction(action);
|
||||
}
|
||||
|
||||
protected runAsyncAction(action: Action): void {
|
||||
const actionName = `record-${action.key}`;
|
||||
const baseRecord = this.store.getBaseRecord();
|
||||
|
||||
this.message.removeMessages();
|
||||
|
||||
const asyncData = {
|
||||
action: actionName,
|
||||
module: baseRecord.module,
|
||||
id: baseRecord.id,
|
||||
} as AsyncActionInput;
|
||||
|
||||
this.asyncActionService.run(actionName, asyncData).pipe(take(1)).subscribe((process: Process) => {
|
||||
if (process.data && process.data.reload) {
|
||||
this.store.load(false).pipe(take(1)).subscribe();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected runFrontEndAction(action: Action): void {
|
||||
const data: SavedFilterActionData = {
|
||||
protected buildActionData(action: Action, context?: ActionContext): SavedFilterActionData {
|
||||
return {
|
||||
store: this.store,
|
||||
listFilterStore: this.listFilterStore,
|
||||
action
|
||||
};
|
||||
} as SavedFilterActionData;
|
||||
}
|
||||
|
||||
this.actionManager.run(action.key, this.store.getMode(), data);
|
||||
protected getMode(): ViewMode {
|
||||
return this.store.getMode();
|
||||
}
|
||||
|
||||
protected getModuleName(context?: ActionContext): string {
|
||||
return this.store.getModuleName();
|
||||
}
|
||||
|
||||
protected reload(action: Action, process: Process, context?: ActionContext): void {
|
||||
this.store.load(false).pipe(take(1)).subscribe();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,25 +35,31 @@
|
|||
|
||||
<div modal-body>
|
||||
|
||||
<scrm-label *ngIf="!tableConfig" labelKey="LBL_CONFIG_NO_CONFIG"></scrm-label>
|
||||
<ng-container *ngIf="!tableConfig">
|
||||
<scrm-label labelKey="LBL_CONFIG_NO_CONFIG"></scrm-label>
|
||||
</ng-container>
|
||||
|
||||
<div *ngIf="tableConfig">
|
||||
<div class="container-fluid">
|
||||
<div class="row pb-3">
|
||||
<div class="col">
|
||||
<scrm-list-filter *ngIf="filterConfig" [config]="filterConfig"></scrm-list-filter>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<scrm-table [config]="tableConfig"></scrm-table>
|
||||
<ng-container *ngIf="tableConfig">
|
||||
<div>
|
||||
<div class="container-fluid">
|
||||
<div class="row pb-3">
|
||||
<div class="col">
|
||||
<scrm-list-filter *ngIf="filterConfig" [config]="filterConfig"></scrm-list-filter>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<scrm-table [config]="tableConfig"></scrm-table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="(loading$ | async) as loading">
|
||||
<scrm-loading-spinner *ngIf="loading" [overlay]="true"></scrm-loading-spinner>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="(loading$ | async) as loading">
|
||||
<scrm-loading-spinner *ngIf="loading" [overlay]="true"></scrm-loading-spinner>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
</scrm-modal>
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
import {Injectable} from '@angular/core';
|
||||
import {Params, Router} from '@angular/router';
|
||||
import {ModuleNameMapper,} from '../../../../services/navigation/module-name-mapper/module-name-mapper.service';
|
||||
import {AttributeMap, isVoid} from 'common';
|
||||
import {AttributeMap, isVoid, ViewMode} from 'common';
|
||||
import get from 'lodash-es/get';
|
||||
import {SubpanelActionData, SubpanelActionHandler} from '../subpanel.action';
|
||||
|
||||
|
@ -37,6 +37,7 @@ import {SubpanelActionData, SubpanelActionHandler} from '../subpanel.action';
|
|||
})
|
||||
export class SubpanelCreateAction extends SubpanelActionHandler {
|
||||
key = 'create';
|
||||
modes = ['list' as ViewMode];
|
||||
|
||||
constructor(
|
||||
protected moduleNameMapper: ModuleNameMapper,
|
||||
|
@ -67,6 +68,10 @@ export class SubpanelCreateAction extends SubpanelActionHandler {
|
|||
}).then();
|
||||
}
|
||||
|
||||
shouldDisplay(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add additional record fields
|
||||
*
|
||||
|
|
|
@ -39,7 +39,7 @@ export interface SubpanelActionHandlerMap {
|
|||
[key: string]: SubpanelActionHandler;
|
||||
}
|
||||
|
||||
export abstract class SubpanelActionHandler extends ActionHandler {
|
||||
export abstract class SubpanelActionHandler extends ActionHandler<SubpanelActionData> {
|
||||
|
||||
abstract run(data: SubpanelActionData): void;
|
||||
}
|
||||
|
|
|
@ -1,89 +0,0 @@
|
|||
/**
|
||||
* 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 {SubpanelActionData, SubpanelActionHandler} from '../subpanel.action';
|
||||
import {MessageModalComponent} from "../../../../components/modal/components/message-modal/message-modal.component";
|
||||
import {Action, ModalButtonInterface} from "common";
|
||||
import {SubpanelActionsAdapter} from "../../adapters/subpanel-actions.adapter";
|
||||
import {NgbModal} from "@ng-bootstrap/ng-bootstrap";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class SubpanelUnlinkAction extends SubpanelActionHandler {
|
||||
key = 'unlink';
|
||||
|
||||
constructor(
|
||||
protected subpanelAdaptor: SubpanelActionsAdapter,
|
||||
private modalService: NgbModal
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
run(data: SubpanelActionData): void {
|
||||
|
||||
this.showConfirmationModal(data);
|
||||
}
|
||||
|
||||
protected showConfirmationModal(data: SubpanelActionData): void {
|
||||
|
||||
const modal = this.modalService.open(MessageModalComponent);
|
||||
|
||||
modal.componentInstance.textKey = 'LBL_UNLINK_RELATIONSHIP_CONFIRM';
|
||||
modal.componentInstance.buttons = [
|
||||
{
|
||||
labelKey: 'LBL_CANCEL',
|
||||
klass: ['btn-secondary'],
|
||||
onClick: activeModal => activeModal.dismiss()
|
||||
} as ModalButtonInterface,
|
||||
{
|
||||
labelKey: 'LBL_PROCEED',
|
||||
klass: ['btn-main'],
|
||||
onClick: activeModal => {
|
||||
|
||||
const action: Action =
|
||||
{
|
||||
key: this.key,
|
||||
asyncProcess: true,
|
||||
params: {
|
||||
store: data.store,
|
||||
payload: {
|
||||
baseModule: data.parentModule,
|
||||
baseRecordId: data.parentId,
|
||||
relateModule: data.store.metadata.get_subpanel_data?? data.module,
|
||||
relateRecordId: data.id
|
||||
}
|
||||
}
|
||||
}
|
||||
this.subpanelAdaptor.runAction(action);
|
||||
|
||||
activeModal.close();
|
||||
}
|
||||
} as ModalButtonInterface,
|
||||
];
|
||||
}
|
||||
}
|
|
@ -25,52 +25,37 @@
|
|||
*/
|
||||
|
||||
import {Injectable} from '@angular/core';
|
||||
import {Action, ActionDataSource} from 'common';
|
||||
import {Observable, of} from 'rxjs';
|
||||
import {take} from 'rxjs/operators';
|
||||
import {AsyncActionInput, AsyncActionService} from '../../../services/process/processes/async-action/async-action';
|
||||
import {AsyncActionService} from '../../../services/process/processes/async-action/async-action';
|
||||
import {MessageService} from '../../../services/message/message.service';
|
||||
import {Process} from '../../../services/process/process.service';
|
||||
import {ConfirmationModalService} from '../../../services/modals/confirmation-modal.service';
|
||||
import {LanguageStore} from '../../../store/language/language.store';
|
||||
import {SubpanelStore} from '../store/subpanel/subpanel.store';
|
||||
import {SubpanelLineActionsAdapter} from './line-actions.adapter';
|
||||
import {SubpanelLineActionManager} from '../line-actions/line-action-manager.service';
|
||||
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class SubpanelActionsAdapter implements ActionDataSource {
|
||||
export class SubpanelLineActionsAdapterFactory {
|
||||
|
||||
constructor(
|
||||
protected actionManager: SubpanelLineActionManager,
|
||||
protected asyncActionService: AsyncActionService,
|
||||
protected message: MessageService
|
||||
protected message: MessageService,
|
||||
protected confirmation: ConfirmationModalService,
|
||||
protected language: LanguageStore
|
||||
) {
|
||||
}
|
||||
|
||||
getActions(): Observable<Action[]> {
|
||||
// not yet implemented
|
||||
return of([]);
|
||||
create(store: SubpanelStore): SubpanelLineActionsAdapter {
|
||||
return new SubpanelLineActionsAdapter(
|
||||
store,
|
||||
this.actionManager,
|
||||
this.asyncActionService,
|
||||
this.message,
|
||||
this.confirmation,
|
||||
this.language
|
||||
);
|
||||
}
|
||||
|
||||
runAction(action: Action): void {
|
||||
if (action.asyncProcess) {
|
||||
this.runAsyncAction(action);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
protected runAsyncAction(action: Action): void {
|
||||
const actionName = `record-${action.key}`;
|
||||
this.message.removeMessages();
|
||||
|
||||
const asyncData = {
|
||||
action: actionName,
|
||||
module: action.params.payload.relateModule,
|
||||
payload: {...action.params.payload}
|
||||
} as AsyncActionInput;
|
||||
|
||||
this.asyncActionService.run(actionName, asyncData).pipe(take(1)).subscribe((process: Process) => {
|
||||
if (process.data.status === 'success' && process.data && process.data.reload) {
|
||||
action.params.store.load(false).pipe(take(1)).subscribe();
|
||||
action.params.store.loadAllStatistics(false).pipe(take(1)).subscribe();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
/**
|
||||
* 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 {Action, ActionContext, ViewMode} from 'common';
|
||||
import {combineLatest, Observable, of} from 'rxjs';
|
||||
import {map, shareReplay, take} from 'rxjs/operators';
|
||||
import {AsyncActionInput, AsyncActionService} from '../../../services/process/processes/async-action/async-action';
|
||||
import {MessageService} from '../../../services/message/message.service';
|
||||
import {Process} from '../../../services/process/process.service';
|
||||
import {ConfirmationModalService} from '../../../services/modals/confirmation-modal.service';
|
||||
import {LanguageStore} from '../../../store/language/language.store';
|
||||
import {BaseRecordActionsAdapter} from '../../../services/actions/base-record-action.adapter';
|
||||
import {SubpanelLineActionData} from '../line-actions/line.action';
|
||||
import {SubpanelStore} from '../store/subpanel/subpanel.store';
|
||||
import {SubpanelLineActionManager} from '../line-actions/line-action-manager.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class SubpanelLineActionsAdapter extends BaseRecordActionsAdapter<SubpanelLineActionData> {
|
||||
|
||||
constructor(
|
||||
protected store: SubpanelStore,
|
||||
protected actionManager: SubpanelLineActionManager,
|
||||
protected asyncActionService: AsyncActionService,
|
||||
protected message: MessageService,
|
||||
protected confirmation: ConfirmationModalService,
|
||||
protected language: LanguageStore
|
||||
) {
|
||||
super(actionManager, asyncActionService, message, confirmation, language)
|
||||
}
|
||||
|
||||
getActions(context: ActionContext = null): Observable<Action[]> {
|
||||
|
||||
return combineLatest(
|
||||
[
|
||||
this.store.metadata$.pipe(map(metadata => metadata.lineActions)),
|
||||
of('list' as ViewMode).pipe(shareReplay()),
|
||||
]
|
||||
).pipe(
|
||||
map(([actions, mode]) => {
|
||||
|
||||
return this.parseModeActions(actions, mode, context);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
protected buildActionData(action: Action, context?: ActionContext): SubpanelLineActionData {
|
||||
return {
|
||||
record: (context && context.record) || null,
|
||||
store: this.store
|
||||
} as SubpanelLineActionData;
|
||||
}
|
||||
|
||||
protected getMode(): ViewMode {
|
||||
return 'list' as ViewMode;
|
||||
}
|
||||
|
||||
protected getModuleName(context?: ActionContext): string {
|
||||
return this.store.metadata.module;
|
||||
}
|
||||
|
||||
protected reload(action: Action, process: Process, context?: ActionContext): void {
|
||||
this.store.load(false).pipe(take(1)).subscribe();
|
||||
this.store.loadAllStatistics(false).pipe(take(1)).subscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build backend process input
|
||||
*
|
||||
* @param action
|
||||
* @param actionName
|
||||
* @param moduleName
|
||||
* @param context
|
||||
*/
|
||||
protected buildActionInput(action: Action, actionName: string, moduleName: string, context: ActionContext = null): AsyncActionInput {
|
||||
|
||||
const metadata = this.store.metadata;
|
||||
const collectionList = metadata.collection_list || null;
|
||||
|
||||
const module = (context && context.module) || moduleName;
|
||||
|
||||
let linkField: string = metadata.get_subpanel_data;
|
||||
|
||||
if(collectionList && collectionList[module] && collectionList[module].get_subpanel_data){
|
||||
linkField = collectionList[module].get_subpanel_data;
|
||||
}
|
||||
|
||||
if(linkField && action && action.params && action.params.linkFieldMapping){
|
||||
Object.keys(action.params.linkFieldMapping).some(key => {
|
||||
if (linkField.includes(key)){
|
||||
linkField = action.params.linkFieldMapping[key];
|
||||
return true;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
action: actionName,
|
||||
module: moduleName,
|
||||
id: (context && context.record && context.record.id) || '',
|
||||
payload: {
|
||||
baseModule: this.store.parentModule,
|
||||
baseRecordId: this.store.parentId,
|
||||
linkField,
|
||||
recordModule: module,
|
||||
relateModule: this.store.metadata.module,
|
||||
relateRecordId: (context && context.record && context.record.id) || '',
|
||||
}
|
||||
} as AsyncActionInput;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
* 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 {SubpanelStore} from '../store/subpanel/subpanel.store';
|
||||
import {SubpanelTableAdapter} from './table.adapter';
|
||||
import {SubpanelLineActionsAdapterFactory} from './line-actions.adapter.factory';
|
||||
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class SubpanelTableAdapterFactory {
|
||||
|
||||
constructor(protected lineActionsAdapterFactory: SubpanelLineActionsAdapterFactory) {
|
||||
}
|
||||
|
||||
create(store: SubpanelStore): SubpanelTableAdapter {
|
||||
return new SubpanelTableAdapter(
|
||||
store,
|
||||
this.lineActionsAdapterFactory
|
||||
);
|
||||
}
|
||||
}
|
|
@ -26,15 +26,19 @@
|
|||
|
||||
import {Observable, of} from 'rxjs';
|
||||
import {Injectable} from '@angular/core';
|
||||
import {ColumnDefinition, LineAction, SortDirection} from 'common';
|
||||
import {ActionDataSource, ColumnDefinition, SortDirection} from 'common';
|
||||
import {map} from 'rxjs/operators';
|
||||
import {TableConfig} from '../../../components/table/table.model';
|
||||
import {SubpanelStore} from '../store/subpanel/subpanel.store';
|
||||
import {SubpanelLineActionsAdapterFactory} from './line-actions.adapter.factory';
|
||||
|
||||
@Injectable()
|
||||
export class SubpanelTableAdapter {
|
||||
|
||||
constructor(protected store: SubpanelStore) {
|
||||
constructor(
|
||||
protected store: SubpanelStore,
|
||||
protected lineActionsAdapterFactory: SubpanelLineActionsAdapterFactory
|
||||
) {
|
||||
}
|
||||
|
||||
getTable(): TableConfig {
|
||||
|
@ -45,7 +49,7 @@ export class SubpanelTableAdapter {
|
|||
module: this.store.metadata.headerModule,
|
||||
|
||||
columns: this.getColumns(),
|
||||
lineActions$: this.getLineActions(),
|
||||
lineActions: this.getLineActions(),
|
||||
sort$: this.store.recordList.sort$,
|
||||
maxColumns$: of(5),
|
||||
loading$: this.store.recordList.loading$,
|
||||
|
@ -67,7 +71,7 @@ export class SubpanelTableAdapter {
|
|||
return this.store.metadata$.pipe(map(metadata => metadata.columns));
|
||||
}
|
||||
|
||||
protected getLineActions(): Observable<LineAction[]> {
|
||||
return this.store.metadata$.pipe(map(metadata => metadata.lineActions));
|
||||
protected getLineActions(): ActionDataSource {
|
||||
return this.lineActionsAdapterFactory.create(this.store);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,6 @@
|
|||
import {Injectable} from '@angular/core';
|
||||
import {SubpanelCreateAction} from '../../actions/create/create.action';
|
||||
import {SubpanelActionData, SubpanelActionHandlerMap} from '../../actions/subpanel.action';
|
||||
import {SubpanelUnlinkAction} from "../../actions/unlink/unlink.action";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
|
@ -38,10 +37,8 @@ export class SubpanelActionManager {
|
|||
|
||||
constructor(
|
||||
protected create: SubpanelCreateAction,
|
||||
protected unlink: SubpanelUnlinkAction,
|
||||
) {
|
||||
this.actions[create.key] = create;
|
||||
this.actions[unlink.key] = unlink;
|
||||
}
|
||||
|
||||
run(action: string, data: SubpanelActionData): void {
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
<scrm-button-group *ngIf="config$" [config$]="config$"></scrm-button-group>
|
||||
</span>
|
||||
<div panel-body>
|
||||
<scrm-table [config]="tableConfig" [store]="store"></scrm-table>
|
||||
<scrm-table [config]="tableConfig"></scrm-table>
|
||||
</div>
|
||||
</scrm-panel>
|
||||
</ng-container>
|
||||
|
|
|
@ -33,6 +33,7 @@ import {SubpanelTableAdapter} from '../../adapters/table.adapter';
|
|||
import {LanguageStore} from '../../../../store/language/language.store';
|
||||
import {SubpanelStore} from '../../store/subpanel/subpanel.store';
|
||||
import {SubpanelActionManager} from './action-manager.service';
|
||||
import {SubpanelTableAdapterFactory} from '../../adapters/table.adapter.factory';
|
||||
|
||||
@Component({
|
||||
selector: 'scrm-subpanel',
|
||||
|
@ -53,11 +54,12 @@ export class SubpanelComponent implements OnInit {
|
|||
constructor(
|
||||
protected actionManager: SubpanelActionManager,
|
||||
protected languages: LanguageStore,
|
||||
protected tableAdapterFactory: SubpanelTableAdapterFactory
|
||||
) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.adapter = new SubpanelTableAdapter(this.store);
|
||||
this.adapter = this.tableAdapterFactory.create(this.store);
|
||||
this.tableConfig = this.adapter.getTable();
|
||||
if (this.maxColumns$) {
|
||||
this.tableConfig.maxColumns$ = this.maxColumns$;
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
* 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 {BaseActionManager} from '../../../services/actions/base-action-manager.service';
|
||||
import {SubpanelLineActionData} from './line.action';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class SubpanelLineActionManager extends BaseActionManager<SubpanelLineActionData> {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/**
|
||||
* 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 {Action, ActionHandler, Record} from 'common';
|
||||
import {LineActionData} from '../../../components/table/line-actions/line.action';
|
||||
import {SubpanelStore} from '../store/subpanel/subpanel.store';
|
||||
|
||||
export interface SubpanelLineActionData extends LineActionData {
|
||||
record: Record;
|
||||
store: SubpanelStore;
|
||||
}
|
||||
|
||||
export abstract class SubpanelLineActionHandler extends ActionHandler<SubpanelLineActionData> {
|
||||
|
||||
abstract run(data: SubpanelLineActionData, action?: Action): void;
|
||||
|
||||
abstract shouldDisplay(data: SubpanelLineActionData): boolean;
|
||||
}
|
|
@ -115,6 +115,10 @@ export * from './components/status-bar/status-bar.module';
|
|||
export * from './components/table/table.component';
|
||||
export * from './components/table/table.model';
|
||||
export * from './components/table/table.module';
|
||||
export * from './components/table/adapters/base-line-actions.adapter';
|
||||
export * from './components/table/line-actions/line-action-manager.service';
|
||||
export * from './components/table/line-actions/line.action';
|
||||
export * from './components/table/line-actions/create-related/create-related.action';
|
||||
export * from './components/table/table-body/table-body.component';
|
||||
export * from './components/table/table-body/table-body.module';
|
||||
export * from './components/table/table-footer/table-footer.component';
|
||||
|
@ -164,8 +168,9 @@ export * from './containers/sidebar-widget/components/statistics-sidebar-widget/
|
|||
export * from './containers/sidebar-widget/components/statistics-sidebar-widget/statistics-sidebar-widget.module';
|
||||
export * from './containers/subpanel/actions/subpanel.action';
|
||||
export * from './containers/subpanel/actions/create/create.action';
|
||||
export * from './containers/subpanel/actions/unlink/unlink.action';
|
||||
export * from './containers/subpanel/adapters/subpanel-actions.adapter';
|
||||
export * from './containers/subpanel/adapters/line-actions.adapter.factory';
|
||||
export * from './containers/subpanel/adapters/line-actions.adapter';
|
||||
export * from './containers/subpanel/adapters/table.adapter.factory';
|
||||
export * from './containers/subpanel/adapters/table.adapter';
|
||||
export * from './containers/subpanel/components/subpanel/action-manager.service';
|
||||
export * from './containers/subpanel/components/subpanel/subpanel.component';
|
||||
|
@ -173,6 +178,8 @@ export * from './containers/subpanel/components/subpanel/subpanel.module';
|
|||
export * from './containers/subpanel/components/subpanel-container/subpanel-container.component';
|
||||
export * from './containers/subpanel/components/subpanel-container/subpanel-container.model';
|
||||
export * from './containers/subpanel/components/subpanel-container/subpanel-container.module';
|
||||
export * from './containers/subpanel/line-actions/line-action-manager.service';
|
||||
export * from './containers/subpanel/line-actions/line.action';
|
||||
export * from './containers/subpanel/store/subpanel/subpanel.store.factory';
|
||||
export * from './containers/subpanel/store/subpanel/subpanel.store';
|
||||
export * from './containers/top-widget/components/statistics-top-widget/statistics-top-widget.component';
|
||||
|
@ -286,6 +293,9 @@ export * from './pipes/format-number/format-number.module';
|
|||
export * from './pipes/format-number/format-number.pipe';
|
||||
export * from './pipes/html-sanitize/html-sanitize.module';
|
||||
export * from './pipes/html-sanitize/html-sanitize.pipe';
|
||||
export * from './services/actions/base-action-manager.service';
|
||||
export * from './services/actions/base-action.adapter';
|
||||
export * from './services/actions/base-record-action.adapter';
|
||||
export * from './services/api/graphql-api/api.collection.get';
|
||||
export * from './services/api/graphql-api/api.entity.get';
|
||||
export * from './services/api/graphql-api/api.record.create';
|
||||
|
@ -381,6 +391,7 @@ export * from './views/create/components/create-view/create-record.component';
|
|||
export * from './views/create/components/create-view/create-record.module';
|
||||
export * from './views/create/store/create-view/create-view.store';
|
||||
export * from './views/list/adapters/filter.adapter';
|
||||
export * from './views/list/adapters/line-actions.adapter';
|
||||
export * from './views/list/adapters/sidebar-widget.adapter';
|
||||
export * from './views/list/adapters/table.adapter';
|
||||
export * from './views/list/components/action-menu/action-menu.component';
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
/**
|
||||
* 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 {Action, ActionData, ActionHandler, ActionHandlerMap, ActionManager, ViewMode} from 'common';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class BaseActionManager<D extends ActionData> implements ActionManager<D> {
|
||||
|
||||
actions: { [key: string]: ActionHandlerMap<D> } = {
|
||||
edit: {} as ActionHandlerMap<D>,
|
||||
create: {} as ActionHandlerMap<D>,
|
||||
list: {} as ActionHandlerMap<D>,
|
||||
detail: {} as ActionHandlerMap<D>
|
||||
};
|
||||
|
||||
run(action: Action, mode: ViewMode, data: D): void {
|
||||
if (!this.actions || !this.actions[mode] || !this.actions[mode][action.key]) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.actions[mode][action.key].run(data, action);
|
||||
}
|
||||
|
||||
getHandler(action: Action, mode: ViewMode): ActionHandler<D> {
|
||||
if (!this.actions || !this.actions[mode] || !this.actions[mode][action.key]) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.actions[mode][action.key];
|
||||
}
|
||||
}
|
230
core/app/core/src/lib/services/actions/base-action.adapter.ts
Normal file
230
core/app/core/src/lib/services/actions/base-action.adapter.ts
Normal file
|
@ -0,0 +1,230 @@
|
|||
/**
|
||||
* 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 {
|
||||
Action,
|
||||
ActionContext,
|
||||
ActionData,
|
||||
ActionDataSource,
|
||||
ActionHandler,
|
||||
ActionManager,
|
||||
ModeActions,
|
||||
ViewMode
|
||||
} from 'common';
|
||||
import {Observable} from 'rxjs';
|
||||
import {take} from 'rxjs/operators';
|
||||
import {AsyncActionInput, AsyncActionService} from '../process/processes/async-action/async-action';
|
||||
import {MessageService} from '../message/message.service';
|
||||
import {Process} from '../process/process.service';
|
||||
import {ConfirmationModalService} from '../modals/confirmation-modal.service';
|
||||
import {LanguageStore} from '../../store/language/language.store';
|
||||
|
||||
export abstract class BaseActionsAdapter<D extends ActionData> implements ActionDataSource {
|
||||
|
||||
defaultActions: ModeActions = {
|
||||
detail: [],
|
||||
list: [],
|
||||
edit: [],
|
||||
create: []
|
||||
};
|
||||
|
||||
protected constructor(
|
||||
protected actionManager: ActionManager<D>,
|
||||
protected asyncActionService: AsyncActionService,
|
||||
protected message: MessageService,
|
||||
protected confirmation: ConfirmationModalService,
|
||||
protected language: LanguageStore
|
||||
) {
|
||||
}
|
||||
|
||||
abstract getActions(context?: ActionContext): Observable<Action[]>;
|
||||
|
||||
protected abstract getModuleName(context?: ActionContext): string;
|
||||
|
||||
protected abstract reload(action: Action, process: Process, context?: ActionContext): void;
|
||||
|
||||
protected abstract getMode(): ViewMode;
|
||||
|
||||
protected abstract buildActionData(action: Action, context?: ActionContext): D;
|
||||
|
||||
/**
|
||||
* Run the action using given context
|
||||
* @param action
|
||||
* @param context
|
||||
*/
|
||||
runAction(action: Action, context: ActionContext = null): void {
|
||||
const params = (action && action.params) || {} as { [key: string]: any };
|
||||
const displayConfirmation = params.displayConfirmation || false;
|
||||
const confirmationLabel = params.confirmationLabel || '';
|
||||
|
||||
if (displayConfirmation) {
|
||||
this.confirmation.showModal(confirmationLabel, () => {
|
||||
this.callAction(action, context);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.callAction(action, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build async process input
|
||||
* @param action
|
||||
* @param actionName
|
||||
* @param moduleName
|
||||
* @param context
|
||||
*/
|
||||
protected abstract buildActionInput(action: Action, actionName: string, moduleName: string, context?: ActionContext): AsyncActionInput;
|
||||
|
||||
/**
|
||||
* Get action name
|
||||
* @param action
|
||||
*/
|
||||
protected getActionName(action: Action) {
|
||||
return `${action.key}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse mode actions
|
||||
* @param declaredActions
|
||||
* @param mode
|
||||
* @param context
|
||||
*/
|
||||
protected parseModeActions(declaredActions: Action[], mode: ViewMode, context: ActionContext = null) {
|
||||
if (!declaredActions) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const availableActions = {
|
||||
list: [],
|
||||
detail: [],
|
||||
edit: [],
|
||||
create: [],
|
||||
} as ModeActions;
|
||||
|
||||
if (declaredActions && declaredActions.length) {
|
||||
declaredActions.forEach(action => {
|
||||
if (!action.modes || !action.modes.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
action.modes.forEach(actionMode => {
|
||||
if (!availableActions[actionMode] && !action.asyncProcess) {
|
||||
return;
|
||||
}
|
||||
availableActions[actionMode].push(action);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
availableActions.detail = availableActions.detail.concat(this.defaultActions.detail);
|
||||
availableActions.list = availableActions.list.concat(this.defaultActions.list);
|
||||
availableActions.edit = availableActions.edit.concat(this.defaultActions.edit);
|
||||
availableActions.create = availableActions.create.concat(this.defaultActions.create);
|
||||
|
||||
const actions = [];
|
||||
|
||||
availableActions[mode].forEach(action => {
|
||||
|
||||
if (!action.asyncProcess) {
|
||||
const actionHandler = this.actionManager.getHandler(action, mode);
|
||||
const data: D = this.buildActionData(action, context);
|
||||
|
||||
if (!this.shouldDisplay(actionHandler, data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
action.status = actionHandler.getStatus(data) || '';
|
||||
}
|
||||
|
||||
const module = (context && context.module) || '';
|
||||
const label = this.language.getFieldLabel(action.labelKey, module);
|
||||
actions.push({
|
||||
...action,
|
||||
label
|
||||
});
|
||||
});
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
protected shouldDisplay(actionHandler: ActionHandler<D>, data: D): boolean {
|
||||
|
||||
return actionHandler && actionHandler.shouldDisplay(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call actions
|
||||
* @param action
|
||||
* @param context
|
||||
*/
|
||||
protected callAction(action: Action, context: ActionContext = null) {
|
||||
if (action.asyncProcess) {
|
||||
this.runAsyncAction(action, context);
|
||||
return;
|
||||
}
|
||||
this.runFrontEndAction(action, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run async actions
|
||||
* @param action
|
||||
* @param context
|
||||
*/
|
||||
protected runAsyncAction(action: Action, context: ActionContext = null): void {
|
||||
const actionName = this.getActionName(action);
|
||||
const moduleName = this.getModuleName(context);
|
||||
|
||||
this.message.removeMessages();
|
||||
const asyncData = this.buildActionInput(action, actionName, moduleName, context);
|
||||
|
||||
this.asyncActionService.run(actionName, asyncData).pipe(take(1)).subscribe((process: Process) => {
|
||||
if (this.shouldReload(process)) {
|
||||
this.reload(action, process, context);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Should reload page
|
||||
* @param process
|
||||
*/
|
||||
protected shouldReload(process: Process): boolean {
|
||||
return !!(process.data && process.data.reload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run front end action
|
||||
* @param action
|
||||
* @param context
|
||||
*/
|
||||
protected runFrontEndAction(action: Action, context: ActionContext = null): void {
|
||||
const data: D = this.buildActionData(action, context);
|
||||
|
||||
this.actionManager.run(action, this.getMode(), data);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
/**
|
||||
* 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 {Action, ActionContext, ActionManager} from 'common';
|
||||
import {AsyncActionInput, AsyncActionService} from '../process/processes/async-action/async-action';
|
||||
import {MessageService} from '../message/message.service';
|
||||
import {ConfirmationModalService} from '../modals/confirmation-modal.service';
|
||||
import {BaseActionsAdapter} from './base-action.adapter';
|
||||
import {LanguageStore} from '../../store/language/language.store';
|
||||
|
||||
@Injectable()
|
||||
export abstract class BaseRecordActionsAdapter<D> extends BaseActionsAdapter<D> {
|
||||
|
||||
|
||||
protected constructor(
|
||||
protected actionManager: ActionManager<D>,
|
||||
protected asyncActionService: AsyncActionService,
|
||||
protected message: MessageService,
|
||||
protected confirmation: ConfirmationModalService,
|
||||
protected language: LanguageStore
|
||||
) {
|
||||
super(
|
||||
actionManager,
|
||||
asyncActionService,
|
||||
message,
|
||||
confirmation,
|
||||
language
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get action name
|
||||
* @param action
|
||||
*/
|
||||
protected getActionName(action: Action) {
|
||||
return `record-${action.key}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build backend process input
|
||||
*
|
||||
* @param action
|
||||
* @param actionName
|
||||
* @param moduleName
|
||||
* @param context
|
||||
*/
|
||||
protected buildActionInput(action: Action, actionName: string, moduleName: string, context: ActionContext = null): AsyncActionInput {
|
||||
return {
|
||||
action: actionName,
|
||||
module: moduleName,
|
||||
id: (context && context.record && context.record.id) || '',
|
||||
} as AsyncActionInput;
|
||||
}
|
||||
}
|
|
@ -42,6 +42,7 @@ export interface AsyncActionInput {
|
|||
criteria?: SearchCriteria;
|
||||
sort?: SortingSelection;
|
||||
ids?: string[];
|
||||
id?: string;
|
||||
payload?: { [key: string]: any };
|
||||
}
|
||||
|
||||
|
|
|
@ -33,7 +33,6 @@ import {
|
|||
ColumnDefinition,
|
||||
deepClone,
|
||||
FieldDefinitionMap,
|
||||
LineAction,
|
||||
ListViewMeta,
|
||||
Panel,
|
||||
SearchMeta,
|
||||
|
@ -112,7 +111,7 @@ export class MetadataStore implements StateStore {
|
|||
* Public long-lived observable streams
|
||||
*/
|
||||
listViewColumns$: Observable<ColumnDefinition[]>;
|
||||
listViewLineActions$: Observable<LineAction[]>;
|
||||
listViewLineActions$: Observable<Action[]>;
|
||||
listMetadata$: Observable<ListViewMeta>;
|
||||
searchMetadata$: Observable<SearchMeta>;
|
||||
recordViewMetadata$: Observable<RecordViewMetadata>;
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
/**
|
||||
* 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 {Action, ActionContext, Record, ViewMode} from 'common';
|
||||
import {combineLatest, Observable, of} from 'rxjs';
|
||||
import {map, shareReplay} from 'rxjs/operators';
|
||||
import {AsyncActionService} from '../../../services/process/processes/async-action/async-action';
|
||||
import {MessageService} from '../../../services/message/message.service';
|
||||
import {Process} from '../../../services/process/process.service';
|
||||
import {ListViewStore} from '../store/list-view/list-view.store';
|
||||
import {LineActionActionManager} from '../../../components/table/line-actions/line-action-manager.service';
|
||||
import {BaseLineActionsAdapter} from '../../../components/table/adapters/base-line-actions.adapter';
|
||||
import {ConfirmationModalService} from '../../../services/modals/confirmation-modal.service';
|
||||
import {LanguageStore} from '../../../store/language/language.store';
|
||||
|
||||
@Injectable()
|
||||
export class LineActionsAdapter extends BaseLineActionsAdapter {
|
||||
|
||||
constructor(
|
||||
protected store: ListViewStore,
|
||||
protected actionManager: LineActionActionManager,
|
||||
protected asyncActionService: AsyncActionService,
|
||||
protected message: MessageService,
|
||||
protected confirmation: ConfirmationModalService,
|
||||
protected language: LanguageStore
|
||||
) {
|
||||
super(
|
||||
actionManager,
|
||||
asyncActionService,
|
||||
message,
|
||||
confirmation,
|
||||
language
|
||||
)
|
||||
}
|
||||
|
||||
getActions(context: ActionContext = null): Observable<Action[]> {
|
||||
return combineLatest(
|
||||
[this.store.lineActions$, of('list' as ViewMode).pipe(shareReplay())]
|
||||
).pipe(
|
||||
map(([lineActions, mode]) => {
|
||||
return this.parseModeActions(lineActions, mode, context);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
protected getModuleName(record?: Record): string {
|
||||
return this.store.getModuleName();
|
||||
}
|
||||
|
||||
protected reload(action: Action, process: Process, record?: Record): void {
|
||||
this.store.recordList.clearSelection();
|
||||
this.store.recordList.resetPagination();
|
||||
}
|
||||
|
||||
protected getMode(): ViewMode {
|
||||
return 'list' as ViewMode;
|
||||
}
|
||||
}
|
|
@ -26,10 +26,16 @@
|
|||
|
||||
import {of} from 'rxjs';
|
||||
import {Injectable} from '@angular/core';
|
||||
import {SortDirection} from 'common';
|
||||
import {ActionDataSource, SortDirection} from 'common';
|
||||
import {ListViewStore} from '../store/list-view/list-view.store';
|
||||
import {MetadataStore} from '../../../store/metadata/metadata.store.service';
|
||||
import {TableConfig} from '../../../components/table/table.model';
|
||||
import {LineActionsAdapter} from './line-actions.adapter';
|
||||
import {LineActionActionManager} from '../../../components/table/line-actions/line-action-manager.service';
|
||||
import {AsyncActionService} from '../../../services/process/processes/async-action/async-action';
|
||||
import {MessageService} from '../../../services/message/message.service';
|
||||
import {ConfirmationModalService} from '../../../services/modals/confirmation-modal.service';
|
||||
import {LanguageStore} from '../../../store/language/language.store';
|
||||
|
||||
@Injectable()
|
||||
export class TableAdapter {
|
||||
|
@ -37,6 +43,11 @@ export class TableAdapter {
|
|||
constructor(
|
||||
protected store: ListViewStore,
|
||||
protected metadata: MetadataStore,
|
||||
protected actionManager: LineActionActionManager,
|
||||
protected asyncActionService: AsyncActionService,
|
||||
protected message: MessageService,
|
||||
protected confirmation: ConfirmationModalService,
|
||||
protected language: LanguageStore
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -48,7 +59,7 @@ export class TableAdapter {
|
|||
module: this.store.getModuleName(),
|
||||
|
||||
columns: this.store.columns$,
|
||||
lineActions$: this.store.lineActions$,
|
||||
lineActions: this.getLineActionsDataSource(),
|
||||
selection$: this.store.selection$,
|
||||
sort$: this.store.sort$,
|
||||
maxColumns$: of(4),
|
||||
|
@ -68,4 +79,16 @@ export class TableAdapter {
|
|||
},
|
||||
} as TableConfig;
|
||||
}
|
||||
|
||||
getLineActionsDataSource(): ActionDataSource {
|
||||
|
||||
return new LineActionsAdapter(
|
||||
this.store,
|
||||
this.actionManager,
|
||||
this.asyncActionService,
|
||||
this.message,
|
||||
this.confirmation,
|
||||
this.language
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,13 +24,12 @@
|
|||
* the words "Supercharged by SuiteCRM".
|
||||
*/
|
||||
|
||||
import {PageSelection, SelectionStatus} from 'common';
|
||||
import {PageSelection, SelectionStatus, SearchCriteriaFieldFilter} from 'common';
|
||||
import {take} from 'rxjs/operators';
|
||||
import {ListViewStore} from './list-view.store';
|
||||
import {listviewMockData, listviewStoreMock} from './list-view.store.spec.mock';
|
||||
import {localStorageServiceMock} from '../../../../services/local-storage/local-storage.service.spec.mock';
|
||||
import {SavedFilter} from '../../../../store/saved-filters/saved-filter.model';
|
||||
import {SearchCriteriaFieldFilter} from '../../../../../../../common/src/lib/views/list/search-criteria.model';
|
||||
|
||||
describe('Listview Store', () => {
|
||||
const service: ListViewStore = listviewStoreMock;
|
||||
|
|
|
@ -25,10 +25,10 @@
|
|||
*/
|
||||
|
||||
import {
|
||||
Action,
|
||||
BulkActionsMap,
|
||||
ColumnDefinition,
|
||||
deepClone,
|
||||
LineAction,
|
||||
ListViewMeta,
|
||||
Pagination,
|
||||
Record,
|
||||
|
@ -120,7 +120,7 @@ export class ListViewStore extends ViewStore implements StateStore,
|
|||
moduleName$: Observable<string>;
|
||||
columns: BehaviorSubject<ColumnDefinition[]>;
|
||||
columns$: Observable<ColumnDefinition[]>;
|
||||
lineActions$: Observable<LineAction[]>;
|
||||
lineActions$: Observable<Action[]>;
|
||||
records$: Observable<Record[]>;
|
||||
criteria$: Observable<SearchCriteria>;
|
||||
context$: Observable<ViewContext>;
|
||||
|
|
|
@ -25,8 +25,7 @@
|
|||
*/
|
||||
|
||||
import {Injectable} from '@angular/core';
|
||||
import {ViewMode} from 'common';
|
||||
import {RecordActionData, RecordActionHandler, RecordActionHandlerMap} from './record.action';
|
||||
import {RecordActionData} from './record.action';
|
||||
import {RecordCancelAction} from './cancel/record-cancel.action';
|
||||
import {RecordSaveAction} from './save/record-save.action';
|
||||
import {RecordToggleWidgetsAction} from './toggle-widgets/record-widget-action.service';
|
||||
|
@ -34,17 +33,12 @@ import {RecordEditAction} from './edit/record-edit.action';
|
|||
import {RecordCreateAction} from './create/record-create.action';
|
||||
import {RecordSaveNewAction} from './save-new/record-save-new.action';
|
||||
import {CancelCreateAction} from './cancel-create/cancel-create.action';
|
||||
import {BaseActionManager} from '../../../services/actions/base-action-manager.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class RecordActionManager {
|
||||
|
||||
actions: { [key: string]: RecordActionHandlerMap } = {
|
||||
edit: {} as RecordActionHandlerMap,
|
||||
detail: {} as RecordActionHandlerMap,
|
||||
create: {} as RecordActionHandlerMap
|
||||
};
|
||||
export class RecordActionManager extends BaseActionManager<RecordActionData> {
|
||||
|
||||
constructor(
|
||||
protected edit: RecordEditAction,
|
||||
|
@ -55,6 +49,7 @@ export class RecordActionManager {
|
|||
protected save: RecordSaveAction,
|
||||
protected saveNew: RecordSaveNewAction,
|
||||
) {
|
||||
super();
|
||||
edit.modes.forEach(mode => this.actions[mode][edit.key] = edit);
|
||||
create.modes.forEach(mode => this.actions[mode][create.key] = create);
|
||||
toggleWidgets.modes.forEach(mode => this.actions[mode][toggleWidgets.key] = toggleWidgets);
|
||||
|
@ -63,20 +58,4 @@ export class RecordActionManager {
|
|||
saveNew.modes.forEach(mode => this.actions[mode][saveNew.key] = saveNew);
|
||||
cancelCreate.modes.forEach(mode => this.actions[mode][cancelCreate.key] = cancelCreate);
|
||||
}
|
||||
|
||||
run(action: string, mode: ViewMode, data: RecordActionData): void {
|
||||
if (!this.actions || !this.actions[mode] || !this.actions[mode][action]) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.actions[mode][action].run(data);
|
||||
}
|
||||
|
||||
getHandler(action: string, mode: ViewMode): RecordActionHandler {
|
||||
if (!this.actions || !this.actions[mode] || !this.actions[mode][action]) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.actions[mode][action];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,26 +24,16 @@
|
|||
* the words "Supercharged by SuiteCRM".
|
||||
*/
|
||||
|
||||
import {ActionData, ActionHandler, ViewMode} from 'common';
|
||||
import {ActionData, ActionHandler} from 'common';
|
||||
import {RecordViewStore} from '../store/record-view/record-view.store';
|
||||
|
||||
export interface RecordActionData extends ActionData {
|
||||
store: RecordViewStore;
|
||||
}
|
||||
|
||||
export interface RecordActionHandlerMap {
|
||||
[key: string]: RecordActionHandler;
|
||||
}
|
||||
|
||||
export abstract class RecordActionHandler extends ActionHandler {
|
||||
|
||||
abstract modes: ViewMode[];
|
||||
|
||||
getStatus(store: RecordViewStore): string {
|
||||
return '';
|
||||
}
|
||||
export abstract class RecordActionHandler extends ActionHandler<RecordActionData> {
|
||||
|
||||
abstract run(data: RecordActionData): void;
|
||||
|
||||
abstract shouldDisplay(store: RecordViewStore): boolean;
|
||||
abstract shouldDisplay(data: RecordActionData): boolean;
|
||||
}
|
||||
|
|
|
@ -27,7 +27,6 @@
|
|||
import {Injectable} from '@angular/core';
|
||||
import {ViewMode} from 'common';
|
||||
import {RecordActionData, RecordActionHandler} from '../record.action';
|
||||
import {RecordViewStore} from '../../store/record-view/record-view.store';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
|
@ -45,11 +44,11 @@ export class RecordToggleWidgetsAction extends RecordActionHandler {
|
|||
data.store.showSidebarWidgets = !data.store.showSidebarWidgets;
|
||||
}
|
||||
|
||||
shouldDisplay(store: RecordViewStore): boolean {
|
||||
return store.widgets;
|
||||
shouldDisplay(data: RecordActionData): boolean {
|
||||
return data.store.widgets;
|
||||
}
|
||||
|
||||
getStatus(store: RecordViewStore): string {
|
||||
return store.showSidebarWidgets ? 'active': '';
|
||||
getStatus(data: RecordActionData): string {
|
||||
return data.store.showSidebarWidgets ? 'active' : '';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
*/
|
||||
|
||||
import {Injectable} from '@angular/core';
|
||||
import {Action, ActionDataSource, ModeActions} from 'common';
|
||||
import {Action, ActionContext, ModeActions, ViewMode} from 'common';
|
||||
import {combineLatest, Observable} from 'rxjs';
|
||||
import {map, take} from 'rxjs/operators';
|
||||
import {MetadataStore} from '../../../store/metadata/metadata.store.service';
|
||||
|
@ -37,9 +37,10 @@ import {LanguageStore} from '../../../store/language/language.store';
|
|||
import {MessageService} from '../../../services/message/message.service';
|
||||
import {Process} from '../../../services/process/process.service';
|
||||
import {ConfirmationModalService} from '../../../services/modals/confirmation-modal.service';
|
||||
import {BaseRecordActionsAdapter} from '../../../services/actions/base-record-action.adapter';
|
||||
|
||||
@Injectable()
|
||||
export class RecordActionsAdapter implements ActionDataSource {
|
||||
export class RecordActionsAdapter extends BaseRecordActionsAdapter<RecordActionData> {
|
||||
|
||||
defaultActions: ModeActions = {
|
||||
detail: [
|
||||
|
@ -89,131 +90,77 @@ export class RecordActionsAdapter implements ActionDataSource {
|
|||
protected actionManager: RecordActionManager,
|
||||
protected asyncActionService: AsyncActionService,
|
||||
protected message: MessageService,
|
||||
protected confimation: ConfirmationModalService
|
||||
protected confirmation: ConfirmationModalService
|
||||
) {
|
||||
super(
|
||||
actionManager,
|
||||
asyncActionService,
|
||||
message,
|
||||
confirmation,
|
||||
language
|
||||
)
|
||||
}
|
||||
|
||||
getActions(): Observable<Action[]> {
|
||||
getActions(context?: ActionContext): Observable<Action[]> {
|
||||
return combineLatest(
|
||||
[
|
||||
this.metadata.recordViewMetadata$,
|
||||
this.store.mode$,
|
||||
this.store.record$,
|
||||
this.language.vm$,
|
||||
this.store.widgets$
|
||||
this.store.language$,
|
||||
this.store.widgets$,
|
||||
]
|
||||
).pipe(
|
||||
map((
|
||||
[
|
||||
meta,
|
||||
mode,
|
||||
record,
|
||||
languages,
|
||||
widget
|
||||
mode
|
||||
]
|
||||
) => {
|
||||
if (!mode || !meta) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const availableActions = {
|
||||
detail: [],
|
||||
edit: [],
|
||||
create: [],
|
||||
} as ModeActions;
|
||||
|
||||
if (meta.actions && meta.actions.length) {
|
||||
meta.actions.forEach(action => {
|
||||
if (!action.modes || !action.modes.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
action.modes.forEach(actionMode => {
|
||||
if (!availableActions[actionMode]) {
|
||||
return;
|
||||
}
|
||||
availableActions[actionMode].push(action);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
availableActions.detail = availableActions.detail.concat(this.defaultActions.detail);
|
||||
availableActions.edit = availableActions.edit.concat(this.defaultActions.edit);
|
||||
availableActions.create = availableActions.create.concat(this.defaultActions.create);
|
||||
|
||||
const actions = [];
|
||||
|
||||
availableActions[mode].forEach(action => {
|
||||
|
||||
if (!action.asyncProcess) {
|
||||
const actionHandler = this.actionManager.getHandler(action.key, mode);
|
||||
|
||||
if (!actionHandler || !actionHandler.shouldDisplay(this.store)) {
|
||||
return;
|
||||
}
|
||||
action.status = actionHandler.getStatus(this.store) || '';
|
||||
}
|
||||
|
||||
const label = this.language.getFieldLabel(action.labelKey, record.module, languages);
|
||||
actions.push({
|
||||
...action,
|
||||
label
|
||||
});
|
||||
});
|
||||
|
||||
return actions;
|
||||
return this.parseModeActions(meta.actions, mode);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
runAction(action: Action): void {
|
||||
const params = (action && action.params) || {} as { [key: string]: any };
|
||||
const displayConfirmation = params.displayConfirmation || false;
|
||||
const confirmationLabel = params.confirmationLabel || '';
|
||||
|
||||
if (displayConfirmation) {
|
||||
this.confimation.showModal(confirmationLabel, () => {
|
||||
this.callAction(action);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.callAction(action);
|
||||
protected buildActionData(action: Action, context?: ActionContext): RecordActionData {
|
||||
return {
|
||||
store: this.store
|
||||
} as RecordActionData;
|
||||
}
|
||||
|
||||
protected callAction(action: Action) {
|
||||
if (action.asyncProcess) {
|
||||
this.runAsyncAction(action);
|
||||
return;
|
||||
}
|
||||
this.runFrontEndAction(action);
|
||||
}
|
||||
|
||||
protected runAsyncAction(action: Action): void {
|
||||
const actionName = `record-${action.key}`;
|
||||
/**
|
||||
* Build backend process input
|
||||
*
|
||||
* @param action
|
||||
* @param actionName
|
||||
* @param moduleName
|
||||
* @param context
|
||||
*/
|
||||
protected buildActionInput(action: Action, actionName: string, moduleName: string, context: ActionContext = null): AsyncActionInput {
|
||||
const baseRecord = this.store.getBaseRecord();
|
||||
|
||||
this.message.removeMessages();
|
||||
|
||||
const asyncData = {
|
||||
return {
|
||||
action: actionName,
|
||||
module: baseRecord.module,
|
||||
id: baseRecord.id,
|
||||
} as AsyncActionInput;
|
||||
|
||||
this.asyncActionService.run(actionName, asyncData).pipe(take(1)).subscribe((process: Process) => {
|
||||
if (process.data && process.data.reload) {
|
||||
this.store.load(false).pipe(take(1)).subscribe();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected runFrontEndAction(action: Action): void {
|
||||
const data: RecordActionData = {
|
||||
store: this.store
|
||||
};
|
||||
protected getMode(): ViewMode {
|
||||
return this.store.getMode();
|
||||
}
|
||||
|
||||
this.actionManager.run(action.key, this.store.getMode(), data);
|
||||
protected getModuleName(context?: ActionContext): string {
|
||||
return this.store.getModuleName();
|
||||
}
|
||||
|
||||
protected reload(action: Action, process: Process, context?: ActionContext): void {
|
||||
this.store.load(false).pipe(take(1)).subscribe();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
import {combineLatest, Observable} from 'rxjs';
|
||||
import {Injectable} from '@angular/core';
|
||||
import {map} from 'rxjs/operators';
|
||||
import {Panel, PanelRow, Record} from 'common';
|
||||
import {Action, Panel, PanelRow, Record} from 'common';
|
||||
import {MetadataStore, RecordViewMetadata} from '../../../store/metadata/metadata.store.service';
|
||||
import {RecordContentConfig, RecordContentDataSource} from '../../../components/record-content/record-content.model';
|
||||
import {RecordActionManager} from '../actions/record-action-manager.service';
|
||||
|
@ -52,7 +52,11 @@ export class RecordContentAdapter implements RecordContentDataSource {
|
|||
store: this.store
|
||||
};
|
||||
|
||||
this.actions.run('edit', this.store.getMode(), data);
|
||||
const action = {
|
||||
key: 'edit'
|
||||
} as Action;
|
||||
|
||||
this.actions.run(action, this.store.getMode(), data);
|
||||
}
|
||||
|
||||
getDisplayConfig(): Observable<RecordContentConfig> {
|
||||
|
|
|
@ -76,6 +76,7 @@
|
|||
<div class="w-100">
|
||||
<scrm-action-group-menu
|
||||
[config]="actionsAdapter"
|
||||
[actionContext]="getActionContext(vm.record)"
|
||||
klass="record-view-actions float-right"
|
||||
buttonClass="settings-button"
|
||||
>
|
||||
|
|
|
@ -82,6 +82,7 @@ import {map} from 'rxjs/operators';
|
|||
import {RecordViewStore} from '../../store/record-view/record-view.store';
|
||||
import {ModuleNavigation} from '../../../../services/navigation/module-navigation/module-navigation.service';
|
||||
import {RecordActionsAdapter} from '../../adapters/actions.adapter';
|
||||
import {ActionContext, Record} from 'common';
|
||||
|
||||
@Component({
|
||||
selector: 'scrm-record-header',
|
||||
|
@ -125,4 +126,19 @@ export class RecordHeaderComponent {
|
|||
getSummaryTemplate(): string {
|
||||
return this.recordViewStore.getSummaryTemplate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build action context
|
||||
* @param record
|
||||
*/
|
||||
getActionContext(record: Record): ActionContext {
|
||||
if (!record) {
|
||||
return {} as ActionContext
|
||||
}
|
||||
|
||||
return {
|
||||
module: record.module || '',
|
||||
record
|
||||
} as ActionContext
|
||||
}
|
||||
}
|
||||
|
|
|
@ -127,7 +127,17 @@ class LineActionDefinitionProvider implements LineActionDefinitionProviderInterf
|
|||
if ($this->checkAccess($relatedModuleName, $actionDefinition['acl']) === false) {
|
||||
continue;
|
||||
}
|
||||
$createActions[] = array_merge($actionTemplate, $relatedModuleDef);
|
||||
|
||||
$action = array_merge($actionTemplate, $relatedModuleDef);
|
||||
$action['modes'] = $action['modes'] ?? ['list'];
|
||||
$action['params'] = $action['params'] ?? [];
|
||||
$action['params']['create'] = $action['params']['create'] ?? [];
|
||||
|
||||
$action['params']['create']['module'] = $action['module'];
|
||||
$action['params']['create']['mapping'] = $action['mapping'] ?? [];
|
||||
$action['params']['create']['legacyModuleName'] = $action['legacyModuleName'] ?? '';
|
||||
$action['params']['create']['action'] = $action['action'] ?? 'edit';
|
||||
$createActions[] = $action;
|
||||
}
|
||||
|
||||
return $createActions;
|
||||
|
|
|
@ -105,6 +105,7 @@ class SystemConfigHandler extends LegacyHandler implements SystemConfigProviderI
|
|||
array $listViewSettingsLimits,
|
||||
array $listViewActionsLimits,
|
||||
array $recordViewActionLimits,
|
||||
array $listViewLineActionsLimits,
|
||||
array $uiConfigs,
|
||||
array $extensions,
|
||||
SessionInterface $session
|
||||
|
@ -123,6 +124,7 @@ class SystemConfigHandler extends LegacyHandler implements SystemConfigProviderI
|
|||
$this->injectedSystemConfigs['listview_settings_limits'] = $listViewSettingsLimits;
|
||||
$this->injectedSystemConfigs['listview_actions_limits'] = $listViewActionsLimits;
|
||||
$this->injectedSystemConfigs['recordview_actions_limits'] = $recordViewActionLimits;
|
||||
$this->injectedSystemConfigs['listview_line_actions_limits'] = $listViewLineActionsLimits;
|
||||
$this->injectedSystemConfigs['ui'] = $uiConfigs;
|
||||
$this->injectedSystemConfigs['extensions'] = $extensions;
|
||||
$this->mappers = $mappers;
|
||||
|
|
|
@ -34,7 +34,6 @@ use App\FieldDefinitions\Service\FieldDefinitionsProviderInterface;
|
|||
use App\Module\Service\ModuleNameMapperInterface;
|
||||
use App\ViewDefinitions\Service\SubPanelDefinitionProviderInterface;
|
||||
use aSubPanel;
|
||||
use Codeception\Step\Action;
|
||||
use SubPanelDefinitions;
|
||||
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||
|
||||
|
@ -81,9 +80,9 @@ class SubPanelDefinitionHandler extends LegacyHandler implements SubPanelDefinit
|
|||
ModuleNameMapperInterface $moduleNameMapper,
|
||||
FieldDefinitionsProviderInterface $fieldDefinitionProvider,
|
||||
SessionInterface $session
|
||||
)
|
||||
{
|
||||
parent::__construct($projectDir, $legacyDir, $legacySessionName, $defaultSessionName, $legacyScopeState, $session);
|
||||
) {
|
||||
parent::__construct($projectDir, $legacyDir, $legacySessionName, $defaultSessionName, $legacyScopeState,
|
||||
$session);
|
||||
$this->moduleNameMapper = $moduleNameMapper;
|
||||
$this->fieldDefinitionProvider = $fieldDefinitionProvider;
|
||||
}
|
||||
|
@ -133,6 +132,7 @@ class SubPanelDefinitionHandler extends LegacyHandler implements SubPanelDefinit
|
|||
|
||||
foreach ($tabs as $key => $tab) {
|
||||
|
||||
/** @var aSubPanel $subpanel */
|
||||
$subpanel = $spd->load_subpanel($key);
|
||||
|
||||
if ($subpanel === false) {
|
||||
|
@ -479,18 +479,19 @@ class SubPanelDefinitionHandler extends LegacyHandler implements SubPanelDefinit
|
|||
}
|
||||
|
||||
/**
|
||||
* @param array $subpanel_def
|
||||
* @param $subpanel_module
|
||||
* @param aSubPanel $subpanelDef
|
||||
* @param string $subpanelModule
|
||||
* @description this function fetches all the line actions defined for a subpanel
|
||||
* for now, only the remove action is filtered from all available line actions
|
||||
* @return array
|
||||
*/
|
||||
public function getSubpanelLineActions($subpanel_def, $subpanel_module): array
|
||||
public function getSubpanelLineActions(aSubPanel $subpanelDef, $subpanelModule): array
|
||||
{
|
||||
$lineActions = [];
|
||||
$unlinkLineAction = [];
|
||||
$subpanelLineActions = ['edit_button', 'close_button', 'remove_button'];
|
||||
|
||||
$thepanel = $subpanel_def->isCollection() ? $subpanel_def->get_header_panel_def() : $subpanel_def;
|
||||
$thepanel = $subpanelDef->isCollection() ? $subpanelDef->get_header_panel_def() : $subpanelDef;
|
||||
|
||||
foreach ($thepanel->get_list_fields() as $field_name => $list_field) {
|
||||
|
||||
|
@ -514,17 +515,28 @@ class SubPanelDefinitionHandler extends LegacyHandler implements SubPanelDefinit
|
|||
}
|
||||
|
||||
if (in_array('remove_button', $lineActions, true)) {
|
||||
|
||||
$unlinkLineAction = [
|
||||
[
|
||||
'key' => 'unlink',
|
||||
'action' => 'unlink',
|
||||
'icon' => 'unlink',
|
||||
'asyncProcess' => true,
|
||||
'labelKey' => 'LBL_UNLINK_RECORD',
|
||||
'module' => $subpanel_module,
|
||||
'module' => $subpanelModule,
|
||||
'routing' => false,
|
||||
'params' => [
|
||||
'linkFieldMapping' => [
|
||||
'get_emails_by_assign_or_link' => 'emails'
|
||||
],
|
||||
'displayConfirmation' => true,
|
||||
'confirmationLabel' => 'LBL_UNLINK_RELATIONSHIP_CONFIRM'
|
||||
],
|
||||
'modes' => ['list']
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
return $unlinkLineAction;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue