Allow conditionally displaying rows and cols on grid widget

This commit is contained in:
Clemente Raposo 2025-06-04 21:27:02 +01:00
parent 82e7e6b58d
commit 32b29954df
3 changed files with 116 additions and 30 deletions

View file

@ -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;
}

View file

@ -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>

View file

@ -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);
});
}
}