mirror of
https://github.com/SuiteCRM/SuiteCRM-Core.git
synced 2025-08-29 08:17:18 +08:00
Allow conditionally displaying rows and cols on grid widget
This commit is contained in:
parent
82e7e6b58d
commit
32b29954df
3 changed files with 116 additions and 30 deletions
|
@ -60,6 +60,8 @@ export interface StatisticWidgetLayoutRow {
|
|||
cols: StatisticWidgetLayoutCol[];
|
||||
class?: string;
|
||||
display?: string;
|
||||
activeOnFields?: any;
|
||||
active?: boolean;
|
||||
}
|
||||
|
||||
export interface StatisticWidgetLayoutCol {
|
||||
|
@ -75,5 +77,10 @@ export interface StatisticWidgetLayoutCol {
|
|||
color?: TextColor;
|
||||
bold?: boolean | string;
|
||||
class?: string;
|
||||
valueClass?: string;
|
||||
labelClass?: string;
|
||||
dynamicLabelClass?: string;
|
||||
params?: { [key: string]: string };
|
||||
activeOnFields?: any;
|
||||
active?: boolean;
|
||||
}
|
||||
|
|
|
@ -29,16 +29,16 @@
|
|||
<scrm-label [labelKey]="this.messageLabelKey"></scrm-label>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!this.messageLabelKey && (vm$| async) as vm"
|
||||
<div *ngIf="!this.messageLabelKey"
|
||||
class="grid-widget d-flex flex-column"
|
||||
ngbTooltip="{{vm.tooltipTitleText}}" placement="auto" container="body">
|
||||
ngbTooltip="{{tooltipTitleText()}}" placement="auto" container="body">
|
||||
|
||||
<ng-container *ngFor="let item of vm.layout">
|
||||
<ng-container *ngFor="let item of layout()">
|
||||
|
||||
<div class="d-flex {{getJustify(item.justify)}} {{getAlign(item.align)}} {{getRowClass()}} {{getLayoutRowClass(item)}}">
|
||||
<div *ngIf="!item?.activeOnFields || (item?.activeOnFields && item?.active)" class="d-flex {{getJustify(item.justify)}} {{getAlign(item.align)}} {{getRowClass()}} {{getLayoutRowClass(item)}}">
|
||||
|
||||
<ng-container *ngFor="let col of item.cols">
|
||||
<div class="{{getColClass()}} {{getClass(col)}}">
|
||||
<div *ngIf="!col?.activeOnFields || (col?.activeOnFields && col?.active)" class="{{getColClass()}} {{getClass(col)}}">
|
||||
|
||||
<ng-container *ngIf="col.display !== 'hidden'">
|
||||
|
||||
|
@ -50,9 +50,9 @@
|
|||
</ng-container>
|
||||
|
||||
<!-- VALUE -->
|
||||
<ng-container *ngIf="col.statistic && (vm.statistics[col.statistic]) as statistics">
|
||||
<ng-container *ngIf="col.statistic && (statisticsMap()[col.statistic]) as statistics">
|
||||
|
||||
<div *ngIf="statistics.field" class="widget-entry-value">
|
||||
<div *ngIf="statistics.field" class="widget-entry-value {{col?.valueClass ?? ''}}">
|
||||
|
||||
<scrm-field [type]="statistics.field.type"
|
||||
[field]="statistics.field"
|
||||
|
@ -74,7 +74,7 @@
|
|||
<!-- LABEL -->
|
||||
<ng-container *ngIf="col.labelKey">
|
||||
|
||||
<div class="widget-entry-label text-truncate">
|
||||
<div class="widget-entry-label text-truncate {{col?.labelClass ?? ''}}">
|
||||
|
||||
<scrm-label [labelKey]="col.labelKey" [module]="getContextModule()"></scrm-label>
|
||||
|
||||
|
@ -87,7 +87,7 @@
|
|||
|
||||
<div class="text-truncate widget-entry-label">
|
||||
|
||||
<label>{{vm.description}}</label>
|
||||
<label>{{description()}}</label>
|
||||
|
||||
</div>
|
||||
|
||||
|
@ -96,10 +96,10 @@
|
|||
<!-- DYNAMIC LABEL -->
|
||||
<ng-container *ngIf="col.dynamicLabel">
|
||||
|
||||
<div *ngIf="!loading" class="widget-entry-dynamic-label text-truncate">
|
||||
<div *ngIf="!loading" class="widget-entry-dynamic-label text-truncate {{col?.dynamicLabelClass ?? ''}}">
|
||||
|
||||
<scrm-dynamic-label [context]="getMessageContext()"
|
||||
[fields]="getMessageFields(vm.statistics)"
|
||||
[fields]="getMessageFields(statisticsMap())"
|
||||
[labelKey]="col.dynamicLabel">
|
||||
</scrm-dynamic-label>
|
||||
|
||||
|
@ -114,7 +114,7 @@
|
|||
<!-- MISCONFIGURATION -->
|
||||
<ng-container *ngIf="!initializing()">
|
||||
<ng-container
|
||||
*ngIf="col.statistic && !loading && (!vm.statistics[col.statistic].field || (vm.statistics[col.statistic].field && isEmptyFieldValue(vm.statistics[col.statistic].field.value)))">
|
||||
*ngIf="col.statistic && !loading && (!statisticsMap()[col.statistic].field || (statisticsMap()[col.statistic].field && isEmptyFieldValue(statisticsMap()[col.statistic].field.value)))">
|
||||
<div class="widget-entry-value {{getSizeClass(col.size)}}">
|
||||
-
|
||||
</div>
|
||||
|
|
|
@ -28,7 +28,9 @@ import {Component, Input, OnDestroy, OnInit, signal, WritableSignal} from '@angu
|
|||
|
||||
import {combineLatestWith, Observable, of, Subscription} from 'rxjs';
|
||||
import {map, shareReplay, take, tap} from 'rxjs/operators';
|
||||
import {SingleValueStatisticsStoreFactory} from '../../store/single-value-statistics/single-value-statistics.store.factory';
|
||||
import {
|
||||
SingleValueStatisticsStoreFactory
|
||||
} from '../../store/single-value-statistics/single-value-statistics.store.factory';
|
||||
import {LanguageStore} from '../../store/language/language.store';
|
||||
import {
|
||||
ContentAlign,
|
||||
|
@ -41,11 +43,18 @@ import {
|
|||
WidgetMetadata
|
||||
} from '../../common/metadata/widget.metadata';
|
||||
import {FieldMap} from '../../common/record/field.model';
|
||||
import {SingleValueStatisticsState, SingleValueStatisticsStoreInterface} from '../../common/statistics/statistics-store.model';
|
||||
import {
|
||||
SingleValueStatisticsState,
|
||||
SingleValueStatisticsStoreInterface
|
||||
} from '../../common/statistics/statistics-store.model';
|
||||
import {StatisticMetadata, StatisticsQuery} from '../../common/statistics/statistics.model';
|
||||
import {StringMap} from '../../common/types/string-map';
|
||||
import {ViewContext} from '../../common/views/view.model';
|
||||
import {isTrue} from '../../common/utils/value-utils';
|
||||
import {ActiveFieldsChecker} from "../../services/condition-operators/active-fields-checker.service";
|
||||
import {Record} from "../../common/record/record.model";
|
||||
import {ObjectMap} from "../../common/types/object-map";
|
||||
|
||||
interface StatisticsEntry {
|
||||
labelKey?: string;
|
||||
type: string;
|
||||
|
@ -94,6 +103,11 @@ export class GridWidgetComponent implements OnInit, OnDestroy {
|
|||
loading = true;
|
||||
messageLabelKey: string;
|
||||
initializing: WritableSignal<boolean> = signal(true);
|
||||
layout: WritableSignal<StatisticWidgetLayoutRow[]> = signal([]);
|
||||
statisticsMap: WritableSignal<StatisticsMap> = signal(null);
|
||||
tooltipTitleText: WritableSignal<string> = signal('');
|
||||
description: WritableSignal<string> = signal('');
|
||||
|
||||
private subs: Subscription[] = [];
|
||||
private statistics: StatisticsEntryMap = {};
|
||||
private loading$: Observable<boolean>;
|
||||
|
@ -101,7 +115,8 @@ export class GridWidgetComponent implements OnInit, OnDestroy {
|
|||
|
||||
constructor(
|
||||
protected language: LanguageStore,
|
||||
protected factory: SingleValueStatisticsStoreFactory
|
||||
protected factory: SingleValueStatisticsStoreFactory,
|
||||
protected activeFieldsChecker: ActiveFieldsChecker
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -120,6 +135,7 @@ export class GridWidgetComponent implements OnInit, OnDestroy {
|
|||
|
||||
ngOnDestroy(): void {
|
||||
this.subs.forEach(sub => sub.unsubscribe());
|
||||
this.subs = [];
|
||||
}
|
||||
|
||||
public validateConfig(): boolean {
|
||||
|
@ -307,7 +323,7 @@ export class GridWidgetComponent implements OnInit, OnDestroy {
|
|||
const rows = this.gridWidgetInput?.layout?.rows ?? [];
|
||||
|
||||
rows.forEach(row => {
|
||||
if (row.display !== 'none'){
|
||||
if (row.display !== 'none') {
|
||||
displayedRows.push(row);
|
||||
}
|
||||
})
|
||||
|
@ -367,9 +383,9 @@ export class GridWidgetComponent implements OnInit, OnDestroy {
|
|||
|
||||
let statisticObs: Observable<boolean[]> = of([]);
|
||||
|
||||
if(loadings$.length < 1) {
|
||||
if (loadings$.length < 1) {
|
||||
statisticObs = of([]);
|
||||
} else if(loadings$.length === 1){
|
||||
} else if (loadings$.length === 1) {
|
||||
statisticObs = loadings$[0].pipe(
|
||||
map(value => [value])
|
||||
);
|
||||
|
@ -431,9 +447,9 @@ export class GridWidgetComponent implements OnInit, OnDestroy {
|
|||
const statistics$: Observable<SingleValueStatisticsState>[] = [];
|
||||
Object.keys(this.statistics).forEach(type => statistics$.push(this.statistics[type].store.state$));
|
||||
|
||||
if(statistics$.length < 1) {
|
||||
if (statistics$.length < 1) {
|
||||
allStatistics$ = of([]);
|
||||
} else if(statistics$.length === 1){
|
||||
} else if (statistics$.length === 1) {
|
||||
allStatistics$ = statistics$[0].pipe(
|
||||
map(value => [value])
|
||||
);
|
||||
|
@ -448,14 +464,14 @@ export class GridWidgetComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
allStatistics$ = allStatistics$.pipe(tap(() => {
|
||||
if(this.initializing()) {
|
||||
if (this.initializing()) {
|
||||
this.initializing.set(false);
|
||||
}
|
||||
}));
|
||||
|
||||
this.vm$ = allStatistics$.pipe(
|
||||
this.subs.push(allStatistics$.pipe(
|
||||
combineLatestWith(layout$),
|
||||
map(([statistics, layout]: [SingleValueStatisticsState[], StatisticWidgetLayoutRow[]]) => {
|
||||
tap(([statistics, layout]: [SingleValueStatisticsState[], StatisticWidgetLayoutRow[]]) => {
|
||||
|
||||
const statsMap: { [key: string]: SingleValueStatisticsState } = {};
|
||||
const tooltipTitles = [];
|
||||
|
@ -474,17 +490,80 @@ export class GridWidgetComponent implements OnInit, OnDestroy {
|
|||
if (description) {
|
||||
descriptions.push(description);
|
||||
}
|
||||
});
|
||||
|
||||
layout.forEach(row => {
|
||||
if (row?.activeOnFields) {
|
||||
|
||||
if (!statsMap || !Object.keys(statsMap).length) {
|
||||
row.active = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const isActive = this.isActive(statsMap, row?.activeOnFields);
|
||||
|
||||
if (isActive) {
|
||||
row.active = true;
|
||||
return;
|
||||
}
|
||||
row.active = false;
|
||||
}
|
||||
|
||||
row.cols.forEach(col => {
|
||||
if (col?.activeOnFields) {
|
||||
|
||||
if (!statsMap || !Object.keys(statsMap).length) {
|
||||
col.active = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const isActive = this.isActive(statsMap, col?.activeOnFields);
|
||||
|
||||
if (isActive) {
|
||||
col.active = true;
|
||||
return;
|
||||
}
|
||||
col.active = false;
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
return {
|
||||
layout,
|
||||
statistics: statsMap,
|
||||
tooltipTitleText: tooltipTitles.join(' | '),
|
||||
description: descriptions.join(' | '),
|
||||
} as GridWidgetState;
|
||||
}));
|
||||
this.statisticsMap.set(statsMap);
|
||||
this.tooltipTitleText.set(tooltipTitles.join(' | '));
|
||||
this.description.set(descriptions.join(' | '));
|
||||
this.layout.set(layout);
|
||||
})).subscribe());
|
||||
}
|
||||
|
||||
protected isActive(statsMap: StatisticsMap, activeOnFields: ObjectMap): boolean {
|
||||
|
||||
const fields = this.getMessageFields(statsMap);
|
||||
const record = {
|
||||
fields: fields
|
||||
} as Record;
|
||||
|
||||
const fieldKeys = Object.keys(activeOnFields);
|
||||
|
||||
if (!activeOnFields || !fieldKeys.length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return fieldKeys.every(fieldKey => {
|
||||
|
||||
const field = fields[fieldKey];
|
||||
|
||||
if (!field) {
|
||||
return true; // If field is not present, consider it active
|
||||
}
|
||||
|
||||
const activeOn = activeOnFields[fieldKey] || null;
|
||||
|
||||
|
||||
return this.activeFieldsChecker.isValueActive(record, field, activeOn);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue