@oneteme/jquery-organizer 0.0.5 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -2
- package/esm2022/lib/group-by-manager/group-by-manager.mjs +3 -1
- package/esm2022/lib/models/organizer-chart-adapter.mjs +1 -17
- package/esm2022/lib/models/organizer-chart-config.mjs +2 -1
- package/esm2022/lib/models/organizer-config.interface.mjs +1 -1
- package/esm2022/lib/models/organizer-utils.mjs +4 -1
- package/esm2022/lib/organizer-button/organizer-button.component.mjs +58 -28
- package/esm2022/lib/organizer-facade/organizer-facade.mjs +31 -21
- package/esm2022/lib/organizer-facade/organizer-facade.types.mjs +1 -1
- package/esm2022/lib/slice-panel/slice-panel.component.mjs +68 -52
- package/esm2022/lib/slice-panel/slice-panel.model.mjs +1 -1
- package/esm2022/public-api.mjs +2 -2
- package/fesm2022/oneteme-jquery-organizer.mjs +162 -206
- package/fesm2022/oneteme-jquery-organizer.mjs.map +1 -1
- package/lib/models/organizer-chart-config.d.ts +3 -1
- package/lib/models/organizer-config.interface.d.ts +10 -1
- package/lib/models/organizer-utils.d.ts +5 -3
- package/lib/organizer-button/organizer-button.component.d.ts +9 -3
- package/lib/organizer-facade/organizer-facade.d.ts +8 -3
- package/lib/organizer-facade/organizer-facade.types.d.ts +2 -2
- package/lib/slice-panel/slice-panel.component.d.ts +7 -2
- package/lib/slice-panel/slice-panel.model.d.ts +4 -4
- package/package.json +2 -2
- package/public-api.d.ts +1 -1
- package/esm2022/lib/lazy-field-manager/lazy-field-manager.mjs +0 -91
- package/lib/lazy-field-manager/lazy-field-manager.d.ts +0 -25
|
@@ -9,7 +9,7 @@ import * as i3 from '@angular/material/menu';
|
|
|
9
9
|
import { MatMenuModule } from '@angular/material/menu';
|
|
10
10
|
import * as i4 from '@angular/material/divider';
|
|
11
11
|
import { MatDividerModule } from '@angular/material/divider';
|
|
12
|
-
import { Subject, isObservable
|
|
12
|
+
import { Subject, isObservable } from 'rxjs';
|
|
13
13
|
import { takeUntil } from 'rxjs/operators';
|
|
14
14
|
|
|
15
15
|
function buildYFields(indicators) {
|
|
@@ -41,10 +41,12 @@ function normalizeOrganizerState(state, config) {
|
|
|
41
41
|
const base = state || {};
|
|
42
42
|
const normalizedY = normalizeYSelection(base.selectedY, base.selectedYAggregate, config.yFields);
|
|
43
43
|
const selectedX = hasOption(config.xFields, base.selectedX) ? base.selectedX : undefined;
|
|
44
|
+
const selectedChartType = hasOption(config.chartTypes, base.selectedChartType) ? base.selectedChartType : undefined;
|
|
44
45
|
const selectedGroupBy = hasOption(config.groups, base.selectedGroupBy) ? base.selectedGroupBy : undefined;
|
|
45
46
|
const selectedSlices = (base.selectedSlices || []).filter(sliceId => hasOption(config.slices, sliceId));
|
|
46
47
|
const normalized = {
|
|
47
48
|
...base,
|
|
49
|
+
selectedChartType,
|
|
48
50
|
selectedX,
|
|
49
51
|
selectedY: normalizedY.selectedY,
|
|
50
52
|
selectedYAggregate: normalizedY.selectedYAggregate,
|
|
@@ -62,6 +64,7 @@ function normalizeTemplateToState(template, currentState, config) {
|
|
|
62
64
|
const nextState = {
|
|
63
65
|
viewMode: base.viewMode,
|
|
64
66
|
visibleFields: base.visibleFields,
|
|
67
|
+
selectedChartType: base.selectedChartType,
|
|
65
68
|
selectedX: hasOption(config.xFields, template.xField) ? template.xField : base.selectedX,
|
|
66
69
|
selectedY: normalizedY.selectedY,
|
|
67
70
|
selectedYAggregate: normalizedY.selectedYAggregate,
|
|
@@ -168,15 +171,11 @@ class OrganizerButtonComponent {
|
|
|
168
171
|
this.sliceStateChange = new EventEmitter();
|
|
169
172
|
this.fieldDataCache = new Map();
|
|
170
173
|
this.destroy$ = new Subject();
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
console.debug('[OrganizerButton] Initialized', {
|
|
174
|
-
configFields: this.config?.fields?.length ?? 0,
|
|
175
|
-
hasState: !!this.state,
|
|
176
|
-
hideMenuValues: this.hideMenuValues
|
|
177
|
-
});
|
|
174
|
+
this.destroyed = false;
|
|
175
|
+
this.sliceRequestId = 0;
|
|
178
176
|
}
|
|
179
177
|
ngOnDestroy() {
|
|
178
|
+
this.destroyed = true;
|
|
180
179
|
this.destroy$.next();
|
|
181
180
|
this.destroy$.complete();
|
|
182
181
|
}
|
|
@@ -193,27 +192,27 @@ class OrganizerButtonComponent {
|
|
|
193
192
|
return `${visible}/${total}`;
|
|
194
193
|
}
|
|
195
194
|
onFieldToggle(fieldId, visible) {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
195
|
+
const current = this.state?.visibleFields
|
|
196
|
+
?? (this.config.fields ?? []).filter(f => f.visible !== false).map(f => f.id);
|
|
197
|
+
let updated;
|
|
198
|
+
if (visible) {
|
|
199
|
+
updated = current.includes(fieldId) ? current : [...current, fieldId];
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
updated = current.filter(f => f !== fieldId);
|
|
203
|
+
}
|
|
201
204
|
this.emitChange('fieldToggled', { visibleFields: updated });
|
|
202
205
|
}
|
|
203
206
|
onXFieldSelect(fieldId) {
|
|
204
|
-
console.debug('[OrganizerButton] X field selected', { fieldId, current: this.state?.selectedX });
|
|
205
207
|
// Évite une requête inutile si le X est déjà sélectionné
|
|
206
208
|
if (this.state?.selectedX === fieldId) {
|
|
207
|
-
console.debug('[OrganizerButton] X field already selected, skipping emit');
|
|
208
209
|
return;
|
|
209
210
|
}
|
|
210
211
|
this.emitChange('xSelected', { selectedX: fieldId });
|
|
211
212
|
}
|
|
212
213
|
onYFieldSelect(fieldId) {
|
|
213
|
-
console.debug('[OrganizerButton] Y field selected', { fieldId, current: this.state?.selectedY });
|
|
214
214
|
// Évite une requête inutile si le Y est déjà sélectionné
|
|
215
215
|
if (this.state?.selectedY === fieldId) {
|
|
216
|
-
console.debug('[OrganizerButton] Y field already selected, skipping emit');
|
|
217
216
|
return;
|
|
218
217
|
}
|
|
219
218
|
this.emitChange('ySelected', {
|
|
@@ -222,10 +221,8 @@ class OrganizerButtonComponent {
|
|
|
222
221
|
});
|
|
223
222
|
}
|
|
224
223
|
onYAggregateSelect(yFieldId, aggregateId) {
|
|
225
|
-
console.debug('[OrganizerButton] Y aggregate selected', { yFieldId, aggregateId, current: this.state?.selectedYAggregate });
|
|
226
224
|
// Évite une requête inutile si l'aggregate est déjà sélectionné
|
|
227
225
|
if (this.state?.selectedYAggregate === aggregateId && this.state?.selectedY === yFieldId) {
|
|
228
|
-
console.debug('[OrganizerButton] Y aggregate already selected, skipping emit');
|
|
229
226
|
return;
|
|
230
227
|
}
|
|
231
228
|
this.emitChange('ySelected', {
|
|
@@ -234,12 +231,16 @@ class OrganizerButtonComponent {
|
|
|
234
231
|
});
|
|
235
232
|
}
|
|
236
233
|
onGroupBySelect(groupId) {
|
|
237
|
-
|
|
238
|
-
const current = this.state.selectedGroupBy;
|
|
234
|
+
const current = this.state?.selectedGroupBy;
|
|
239
235
|
this.emitChange('groupBySelected', { selectedGroupBy: current === groupId ? undefined : groupId });
|
|
240
236
|
}
|
|
237
|
+
onChartTypeSelect(chartType) {
|
|
238
|
+
const option = this.config.chartTypes?.find(type => type.id === chartType);
|
|
239
|
+
if (!option || option.disabled || this.state?.selectedChartType === chartType)
|
|
240
|
+
return;
|
|
241
|
+
this.emitChange('chartTypeSelected', { selectedChartType: chartType });
|
|
242
|
+
}
|
|
241
243
|
onTemplateSelect(templateId) {
|
|
242
|
-
console.debug('[OrganizerButton] Template selected', { templateId });
|
|
243
244
|
if (!this.config.templates?.length)
|
|
244
245
|
return;
|
|
245
246
|
const template = this.config.templates?.find(t => t.id === templateId);
|
|
@@ -250,7 +251,6 @@ class OrganizerButtonComponent {
|
|
|
250
251
|
this.syncSliceState(newState.selectedSlices);
|
|
251
252
|
}
|
|
252
253
|
onSliceClick(sliceId) {
|
|
253
|
-
console.debug('[OrganizerButton] Slice clicked', { sliceId });
|
|
254
254
|
if (this.config.onSliceClick) {
|
|
255
255
|
this.config.onSliceClick();
|
|
256
256
|
}
|
|
@@ -274,6 +274,10 @@ class OrganizerButtonComponent {
|
|
|
274
274
|
});
|
|
275
275
|
}
|
|
276
276
|
syncSliceState(selectedSlices, onLoaded) {
|
|
277
|
+
// Une nouvelle demande annule la précédente : seule la réponse la plus récente est prise en compte.
|
|
278
|
+
const requestId = ++this.sliceRequestId;
|
|
279
|
+
this.sliceSubscription?.unsubscribe();
|
|
280
|
+
this.sliceSubscription = undefined;
|
|
277
281
|
const sliceId = selectedSlices?.[0];
|
|
278
282
|
if (!sliceId || !this.config.onFetchSliceData) {
|
|
279
283
|
this.sliceStateChange.emit(null);
|
|
@@ -288,6 +292,8 @@ class OrganizerButtonComponent {
|
|
|
288
292
|
}
|
|
289
293
|
const result = this.config.onFetchSliceData(sliceId);
|
|
290
294
|
const handleData = (tasks) => {
|
|
295
|
+
if (this.destroyed || requestId !== this.sliceRequestId)
|
|
296
|
+
return;
|
|
291
297
|
this.sliceStateChange.emit({
|
|
292
298
|
sliceConfigs: [{ title: slice.label ?? sliceId, columnKey: sliceId }],
|
|
293
299
|
tasks,
|
|
@@ -295,11 +301,18 @@ class OrganizerButtonComponent {
|
|
|
295
301
|
});
|
|
296
302
|
onLoaded?.();
|
|
297
303
|
};
|
|
304
|
+
const handleError = (error) => {
|
|
305
|
+
if (this.destroyed || requestId !== this.sliceRequestId)
|
|
306
|
+
return;
|
|
307
|
+
console.error('[OrganizerButton] Slice data loading failed', error);
|
|
308
|
+
this.sliceStateChange.emit(null);
|
|
309
|
+
onLoaded?.();
|
|
310
|
+
};
|
|
298
311
|
if (isObservable(result)) {
|
|
299
|
-
result.pipe(takeUntil(this.destroy$)).subscribe({ next: handleData });
|
|
312
|
+
this.sliceSubscription = result.pipe(takeUntil(this.destroy$)).subscribe({ next: handleData, error: handleError });
|
|
300
313
|
}
|
|
301
314
|
else {
|
|
302
|
-
result.then(handleData);
|
|
315
|
+
void result.then(handleData, handleError);
|
|
303
316
|
}
|
|
304
317
|
}
|
|
305
318
|
async loadFieldData(fieldId) {
|
|
@@ -316,6 +329,9 @@ class OrganizerButtonComponent {
|
|
|
316
329
|
const data = await this.config.onFetchFieldData(fieldId);
|
|
317
330
|
this.fieldDataCache.set(fieldId, data);
|
|
318
331
|
}
|
|
332
|
+
catch (error) {
|
|
333
|
+
console.error('[OrganizerButton] Field data loading failed', error);
|
|
334
|
+
}
|
|
319
335
|
finally {
|
|
320
336
|
this.loadingFieldId = undefined;
|
|
321
337
|
this.cdr.markForCheck();
|
|
@@ -351,6 +367,7 @@ class OrganizerButtonComponent {
|
|
|
351
367
|
hasMenuItemsBeforeActions() {
|
|
352
368
|
return this.hasChartFields()
|
|
353
369
|
|| this.hasTableFields()
|
|
370
|
+
|| (this.config.chartTypes?.length ?? 0) > 0
|
|
354
371
|
|| (this.config.groups?.length ?? 0) > 0
|
|
355
372
|
|| (this.config.slices?.length ?? 0) > 0
|
|
356
373
|
|| (this.config.templates?.length ?? 0) > 0;
|
|
@@ -402,14 +419,18 @@ class OrganizerButtonComponent {
|
|
|
402
419
|
this.mainMenuTrigger?.closeMenu();
|
|
403
420
|
}
|
|
404
421
|
onReset() {
|
|
422
|
+
const visibleFields = this.config.fields
|
|
423
|
+
?.filter(field => field.visible !== false)
|
|
424
|
+
.map(field => field.id);
|
|
405
425
|
this.emitChange('reset', {
|
|
406
|
-
visibleFields
|
|
426
|
+
visibleFields,
|
|
407
427
|
selectedX: undefined,
|
|
408
428
|
selectedY: undefined,
|
|
409
429
|
selectedYAggregate: undefined,
|
|
410
430
|
selectedGroupBy: undefined,
|
|
411
431
|
selectedSlices: []
|
|
412
432
|
});
|
|
433
|
+
this.mainMenuTrigger?.closeMenu();
|
|
413
434
|
}
|
|
414
435
|
activeXLabel() {
|
|
415
436
|
const id = this.state?.selectedX;
|
|
@@ -435,6 +456,12 @@ class OrganizerButtonComponent {
|
|
|
435
456
|
return '';
|
|
436
457
|
return this.config.groups?.find(g => g.id === id)?.label ?? id;
|
|
437
458
|
}
|
|
459
|
+
activeChartTypeLabel() {
|
|
460
|
+
const id = this.state?.selectedChartType;
|
|
461
|
+
if (!id)
|
|
462
|
+
return '';
|
|
463
|
+
return this.config.chartTypes?.find(type => type.id === id)?.label ?? id;
|
|
464
|
+
}
|
|
438
465
|
activeSlicesLabel() {
|
|
439
466
|
const ids = this.state?.selectedSlices;
|
|
440
467
|
if (!ids?.length)
|
|
@@ -453,6 +480,9 @@ class OrganizerButtonComponent {
|
|
|
453
480
|
hasChartFields() {
|
|
454
481
|
return (this.config.xFields?.length ?? 0) > 0 || (this.config.yFields?.length ?? 0) > 0;
|
|
455
482
|
}
|
|
483
|
+
hasChartTypes() {
|
|
484
|
+
return (this.config.chartTypes?.length ?? 0) > 0;
|
|
485
|
+
}
|
|
456
486
|
hasTableFields() {
|
|
457
487
|
return (this.config.fields?.length ?? 0) > 0;
|
|
458
488
|
}
|
|
@@ -462,6 +492,9 @@ class OrganizerButtonComponent {
|
|
|
462
492
|
isYAggregateActive(yFieldId, aggregateId) {
|
|
463
493
|
return this.state?.selectedY === yFieldId && this.state?.selectedYAggregate === aggregateId;
|
|
464
494
|
}
|
|
495
|
+
isChartTypeActive(chartType) {
|
|
496
|
+
return this.state?.selectedChartType === chartType;
|
|
497
|
+
}
|
|
465
498
|
emitChange(type, stateUpdate) {
|
|
466
499
|
const isFullState = 'selectedX' in stateUpdate && 'selectedTemplate' in stateUpdate;
|
|
467
500
|
const rawState = isFullState ? stateUpdate : { ...this.state, ...stateUpdate };
|
|
@@ -477,11 +510,11 @@ class OrganizerButtonComponent {
|
|
|
477
510
|
this.viewChange.emit(event);
|
|
478
511
|
}
|
|
479
512
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: OrganizerButtonComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
480
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: OrganizerButtonComponent, isStandalone: true, selector: "organizer-button", inputs: { config: "config", state: "state", hideMenuValues: "hideMenuValues" }, outputs: { viewChange: "viewChange", sliceStateChange: "sliceStateChange" }, viewQueries: [{ propertyName: "mainMenuTrigger", first: true, predicate: ["mainMenuTrigger"], descendants: true }], ngImport: i0, template: "<button type=\"button\" class=\"organizer-btn\" [matMenuTriggerFor]=\"viewMenu\" #mainMenuTrigger=\"matMenuTrigger\" [attr.aria-label]=\"'View'\">\n <mat-icon class=\"organizer-btn-icon\">{{ config.buttonIcon || 'settings' }}</mat-icon>\n</button>\n\n<mat-menu #viewMenu=\"matMenu\" panelClass=\"organizer-menu-panel\">\n\n <!-- Champs : mode chart (X + Y) -->\n <button *ngIf=\"hasChartFields()\" mat-menu-item [matMenuTriggerFor]=\"champsSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Champs</span>\n <span class=\"organizer-menu-value\" *ngIf=\"!hideMenuValues && (activeXLabel() || activeYLabel())\">\n {{ activeXLabel() }}<ng-container *ngIf=\"activeXLabel() && activeYLabel()\"> / </ng-container>{{ activeYLabel() }}\n </span>\n </button>\n\n <!-- Champs : mode table (toggle colonnes) -->\n <button *ngIf=\"hasTableFields()\" mat-menu-item [matMenuTriggerFor]=\"fieldsSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Champs</span>\n <span class=\"organizer-menu-value\" *ngIf=\"!hideMenuValues\">{{ fieldsSummary() }}</span>\n </button>\n\n <!-- Grouper par -->\n <button *ngIf=\"config.groups?.length\" mat-menu-item [matMenuTriggerFor]=\"groupsSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Grouper par</span>\n <span class=\"organizer-menu-value\" *ngIf=\"!hideMenuValues && activeGroupByLabel()\">{{ activeGroupByLabel() }}</span>\n </button>\n\n <!-- Filtrer par -->\n <button *ngIf=\"config.slices?.length\" mat-menu-item [matMenuTriggerFor]=\"slicesSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Filtrer par</span>\n <span class=\"organizer-menu-value\" *ngIf=\"!hideMenuValues && activeSlicesLabel()\">{{ activeSlicesLabel() }}</span>\n </button>\n\n <!-- Template -->\n <button *ngIf=\"config.templates?.length\" mat-menu-item [matMenuTriggerFor]=\"templateSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Template</span>\n <span class=\"organizer-menu-value\" *ngIf=\"!hideMenuValues && activeTemplateLabel()\">{{ activeTemplateLabel() }}</span>\n </button>\n\n <!-- Actions du visuel courant -->\n <mat-divider *ngIf=\"hasActions() && hasMenuItemsBeforeActions()\" class=\"organizer-divider\"></mat-divider>\n <button *ngIf=\"hasActions() && hasOtherMenuItems()\" mat-menu-item [matMenuTriggerFor]=\"actionsSubMenu\" class=\"organizer-menu-trigger\">\n <mat-icon *ngIf=\"config.actions?.icon\" class=\"organizer-menu-export-icon\">{{ config.actions?.icon }}</mat-icon>\n <span class=\"organizer-menu-label\">{{ actionsLabel() }}</span>\n </button>\n\n <!-- Actions directes lorsqu'il n'y a aucun autre item principal -->\n <ng-container *ngIf=\"hasActions() && !hasOtherMenuItems()\">\n <button *ngIf=\"isCopyActionVisible()\" mat-menu-item type=\"button\" (click)=\"onCopyVisual()\">\n <mat-icon class=\"organizer-menu-export-icon\">content_copy</mat-icon>\n <span class=\"organizer-menu-label\">Copier</span>\n </button>\n <button *ngIf=\"isFullscreenActionVisible()\" mat-menu-item type=\"button\" (click)=\"onToggleFullscreen()\">\n <mat-icon class=\"organizer-menu-export-icon\">{{ config.isFullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon>\n <span class=\"organizer-menu-label\">{{ config.isFullscreen ? 'Quitter le plein \u00E9cran' : 'Plein \u00E9cran' }}</span>\n </button>\n </ng-container>\n\n <!-- Export direct (mode table) -->\n <mat-divider *ngIf=\"config.showExport\" class=\"organizer-divider\"></mat-divider>\n <button *ngIf=\"config.showExport && !hasExportSubMenu()\" mat-menu-item type=\"button\" class=\"organizer-menu-export\" (click)=\"onExport()\">\n <mat-icon class=\"organizer-menu-export-icon\">download</mat-icon>\n <span class=\"organizer-menu-label\">Exporter</span>\n </button>\n\n <!-- Export graphique (mode chart) \u2014 ouvre un sous-menu -->\n <button *ngIf=\"config.showExport && hasExportSubMenu()\" mat-menu-item [matMenuTriggerFor]=\"exportSubMenu\" class=\"organizer-menu-trigger organizer-menu-export\">\n <mat-icon class=\"organizer-menu-export-icon\">download</mat-icon>\n <span class=\"organizer-menu-label\">Exporter</span>\n </button>\n\n <!-- Pr\u00E9f\u00E9rences -->\n <mat-divider *ngIf=\"config.showPreferences\" class=\"organizer-divider\"></mat-divider>\n <button *ngIf=\"config.showPreferences\" mat-menu-item [matMenuTriggerFor]=\"preferencesSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Pr\u00E9f\u00E9rences</span>\n </button>\n\n <!-- Switch chart / table -->\n <mat-divider *ngIf=\"hasSwitchView()\" class=\"organizer-divider\"></mat-divider>\n <button *ngIf=\"hasSwitchView()\" mat-menu-item type=\"button\" class=\"organizer-menu-trigger\" (click)=\"onSwitchView()\">\n <mat-icon class=\"organizer-menu-export-icon\">{{ config.switchView?.currentView === 'chart' ? 'table_chart' : 'bar_chart' }}</mat-icon>\n <span class=\"organizer-menu-label\">{{ config.switchView?.currentView === 'chart' ? 'Tableau' : 'Graphique' }}</span>\n </button>\n\n</mat-menu>\n\n<!-- Sous-menu : actions du visuel -->\n<mat-menu #actionsSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button *ngIf=\"isCopyActionVisible()\" type=\"button\" class=\"organizer-submenu-item\"\n (mousedown)=\"onCopyVisual()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">content_copy</mat-icon>\n Copier\n </button>\n <button *ngIf=\"isFullscreenActionVisible()\" type=\"button\" class=\"organizer-submenu-item\"\n (mousedown)=\"onToggleFullscreen()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">{{ config.isFullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon>\n {{ config.isFullscreen ? 'Quitter le plein \u00E9cran' : 'Plein \u00E9cran' }}\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : Export graphique -->\n<mat-menu #exportSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button *ngIf=\"config.onExportVisual\" type=\"button\" class=\"organizer-submenu-item\"\n (mousedown)=\"onExportVisual()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">image</mat-icon>\n Visuel\n </button>\n <button *ngIf=\"config.onExportData\" type=\"button\" class=\"organizer-submenu-item\"\n (mousedown)=\"onExportData()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">table_chart</mat-icon>\n Donn\u00E9es\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : Champs chart (X + Y) -->\n<mat-menu #champsSubMenu=\"matMenu\" panelClass=\"organizer-champs-panel\">\n <div class=\"organizer-champs-wrapper\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n\n <!-- Bloc X -->\n <div class=\"organizer-champs-block\" *ngIf=\"config.xFields?.length\">\n <span class=\"organizer-champs-block-title\">Axe horizontal \u2014 X</span>\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let xField of config.xFields\"\n [class.active]=\"state?.selectedX === xField.id\"\n [disabled]=\"xField.disabled\"\n (mousedown)=\"onXFieldSelect(xField.id)\">\n <span class=\"organizer-submenu-check\">{{ state?.selectedX === xField.id ? '\u2713' : '' }}</span>\n {{ xField.label }}\n </button>\n </div>\n\n <mat-divider *ngIf=\"config.xFields?.length && config.yFields?.length\" class=\"organizer-champs-divider\"></mat-divider>\n\n <!-- Bloc Y -->\n <div class=\"organizer-champs-block\" *ngIf=\"config.yFields?.length\">\n <span class=\"organizer-champs-block-title\">Axe vertical \u2014 Y</span>\n <ng-container *ngFor=\"let yField of config.yFields\">\n\n <!-- Y simple (Count sans agr\u00E9gat ni stack) -->\n <!-- Y simple (Count ou m\u00E9trique directe) -->\n <button *ngIf=\"!yField.aggregates?.length\"\n type=\"button\" class=\"organizer-submenu-item\"\n [class.active]=\"isYActive(yField.id)\"\n [disabled]=\"yField.disabled\"\n (mousedown)=\"onYFieldSelect(yField.id)\">\n <span class=\"organizer-submenu-check\">{{ isYActive(yField.id) ? '\u2713' : '' }}</span>\n {{ yField.label }}\n </button>\n\n <!-- Y avec agr\u00E9gats \u2192 sous-menu -->\n <button *ngIf=\"yField.aggregates?.length\"\n type=\"button\" class=\"organizer-submenu-item organizer-submenu-item--has-children\"\n [class.active]=\"isYActive(yField.id)\"\n [disabled]=\"yField.disabled\"\n [matMenuTriggerFor]=\"yAggSubMenu\"\n [matMenuTriggerData]=\"{ yField: yField }\"\n (mousedown)=\"$event.stopPropagation()\">\n <span class=\"organizer-submenu-check\">{{ isYActive(yField.id) ? '\u2713' : '' }}</span>\n {{ yField.label }}\n <mat-icon class=\"organizer-submenu-arrow\">chevron_right</mat-icon>\n </button>\n\n </ng-container>\n </div>\n\n </div>\n</mat-menu>\n\n<!-- Sous-menu dynamique : agr\u00E9gats d'un champ Y -->\n<mat-menu #yAggSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <ng-template matMenuContent let-yField=\"yField\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let agg of yField.aggregates\"\n [class.active]=\"isYAggregateActive(yField.id, agg.id)\"\n (mousedown)=\"onYAggregateSelect(yField.id, agg.id)\">\n <span class=\"organizer-submenu-check\">{{ isYAggregateActive(yField.id, agg.id) ? '\u2713' : '' }}</span>\n {{ agg.label }}\n </button>\n </div>\n </ng-template>\n</mat-menu>\n\n<!-- Sous-menu : Champs table (toggle colonnes) -->\n<mat-menu #fieldsSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let field of config.fields\"\n [class.active]=\"isFieldVisible(field.id)\"\n (mousedown)=\"onFieldToggle(field.id, !isFieldVisible(field.id))\">\n <span class=\"organizer-submenu-check\">{{ isFieldVisible(field.id) ? '\u2713' : '' }}</span> <mat-icon *ngIf=\"field.icon\" class=\"organizer-submenu-icon\">{{ field.icon }}</mat-icon> {{ field.label }}\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : Grouper par -->\n<mat-menu #groupsSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let group of config.groups\"\n [class.active]=\"state?.selectedGroupBy === group.id\"\n (mousedown)=\"onGroupBySelect(group.id)\">\n <span class=\"organizer-submenu-check\">{{ state?.selectedGroupBy === group.id ? '\u2713' : '' }}</span> <mat-icon *ngIf=\"group.icon\" class=\"organizer-submenu-icon\">{{ group.icon }}</mat-icon> {{ group.label }}\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : Filtrer par -->\n<mat-menu #slicesSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let slice of config.slices\"\n [class.active]=\"state?.selectedSlices?.includes(slice.id)\"\n (mousedown)=\"onSliceClick(slice.id)\">\n <span class=\"organizer-submenu-check\">{{ state?.selectedSlices?.includes(slice.id) ? '\u2713' : '' }}</span> <mat-icon *ngIf=\"slice.icon\" class=\"organizer-submenu-icon\">{{ slice.icon }}</mat-icon> {{ slice.label }}\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : Template -->\n<mat-menu #templateSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let tmpl of config.templates\"\n [class.active]=\"state?.selectedTemplate === tmpl.id\"\n [title]=\"tmpl.description || ''\"\n (mousedown)=\"onTemplateSelect(tmpl.id)\">\n <span class=\"organizer-submenu-check\">{{ state?.selectedTemplate === tmpl.id ? '\u2713' : '' }}</span>\n <mat-icon *ngIf=\"tmpl.icon\" class=\"organizer-submenu-icon\">{{ tmpl.icon }}</mat-icon>\n {{ tmpl.label }}\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : Pr\u00E9f\u00E9rences -->\n<mat-menu #preferencesSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\" (mousedown)=\"onPreferencesEdit()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">edit</mat-icon>\u00C9diter\n </button>\n <button type=\"button\" class=\"organizer-submenu-item\" (mousedown)=\"onPreferencesSave()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">save</mat-icon>Sauvegarder\n </button>\n <button type=\"button\" class=\"organizer-submenu-item\" [disabled]=\"!config.hasSavedPreferences\" (mousedown)=\"onPreferencesClear()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">delete_outline</mat-icon>R\u00E9initialiser\n </button>\n </div>\n</mat-menu>\n\n", styles: [":host{display:inline-flex;align-items:center;justify-content:center;line-height:0}.organizer-btn{position:relative;display:block;height:34px;width:34px;border:1px solid #e0e0e0;border-radius:8px;background:#fff;padding:0;overflow:hidden;cursor:pointer;box-sizing:border-box}.organizer-btn:hover{border-color:#9e9e9e}.organizer-btn-icon.mat-icon,.organizer-btn-icon.material-icons{position:absolute;inset:0;display:block!important;width:18px!important;height:18px!important;margin:auto!important;padding:0!important;font-size:18px!important;line-height:18px!important;text-align:center;vertical-align:middle;transform:none;color:#000}.mat-mdc-menu-panel:has(.organizer-menu-label){min-width:170px!important;max-width:320px!important;overflow:visible!important}.mat-mdc-menu-panel:has(.organizer-menu-label) .mat-mdc-menu-content{padding:4px 0!important}.mat-mdc-menu-panel:has(.organizer-menu-label) .organizer-menu-trigger.mat-mdc-menu-item{min-height:42px;padding:0 14px;display:flex;align-items:center;box-sizing:border-box}.mat-mdc-menu-panel:has(.organizer-menu-label) .organizer-menu-trigger.mat-mdc-menu-item .mdc-list-item__primary-text{display:flex;align-items:center;width:100%;overflow:hidden}.organizer-menu-label{font-size:13px;font-weight:500;color:#212121;flex:0 0 auto;white-space:nowrap;padding-right:8px}.organizer-menu-value{font-size:12px;color:#757575;flex:1 1 auto;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-align:right}.organizer-submenu{padding:4px 6px;min-width:180px;box-sizing:border-box}.organizer-submenu .organizer-submenu-item{display:flex;align-items:center;gap:6px;width:100%;padding:7px 8px;border:none;background:transparent;border-radius:6px;font-size:13px;color:#212121;cursor:pointer;text-align:left;box-sizing:border-box;white-space:nowrap}.organizer-submenu .organizer-submenu-item:hover{background:#f5f5f5}.organizer-submenu .organizer-submenu-item.active{font-weight:600}.organizer-submenu-check{flex-shrink:0;width:16px;text-align:center;font-size:12px;color:#212121}.organizer-submenu-icon{font-size:14px!important;width:14px!important;height:14px!important;color:#212121;margin-right:4px}.organizer-menu-export.mat-mdc-menu-item{min-height:42px;padding:0 14px}.organizer-menu-export-icon{font-size:16px!important;width:16px!important;height:16px!important;color:#424242;margin-right:8px}.organizer-divider{margin:4px 0!important}.organizer-champs-wrapper{padding:4px 6px;min-width:200px;box-sizing:border-box;display:flex;flex-direction:column}.organizer-champs-block{display:flex;flex-direction:column}.organizer-champs-block-title{font-size:10px;font-weight:700;letter-spacing:.6px;text-transform:uppercase;color:#9e9e9e;padding:6px 2px 2px}.organizer-champs-divider{margin:4px 0!important}.organizer-champs-wrapper .organizer-submenu-item{display:flex;align-items:center;gap:6px;width:100%;padding:7px 8px;border:none;background:transparent;border-radius:6px;font-size:13px;font-family:inherit;color:#212121;cursor:pointer;text-align:left;box-sizing:border-box;white-space:nowrap}.organizer-champs-wrapper .organizer-submenu-item:hover:not([disabled]){background:#f5f5f5}.organizer-champs-wrapper .organizer-submenu-item.active{font-weight:600}.organizer-champs-wrapper .organizer-submenu-item[disabled]{opacity:.38;cursor:default}.organizer-champs-wrapper .organizer-submenu-check{flex-shrink:0;width:16px;text-align:center;font-size:12px;color:#212121}.organizer-champs-wrapper .organizer-submenu-item--has-children{padding-right:8px}.organizer-champs-wrapper .organizer-submenu-arrow{font-size:18px!important;width:18px!important;height:18px!important;color:#00000061;margin-left:auto}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i3.MatMenu, selector: "mat-menu", exportAs: ["matMenu"] }, { kind: "component", type: i3.MatMenuItem, selector: "[mat-menu-item]", inputs: ["disabled", "disableRipple", "role"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i3.MatMenuContent, selector: "ng-template[matMenuContent]" }, { kind: "directive", type: i3.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", exportAs: ["matMenuTrigger"] }, { kind: "ngmodule", type: MatDividerModule }, { kind: "component", type: i4.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
|
|
513
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: OrganizerButtonComponent, isStandalone: true, selector: "organizer-button", inputs: { config: "config", state: "state", hideMenuValues: "hideMenuValues" }, outputs: { viewChange: "viewChange", sliceStateChange: "sliceStateChange" }, viewQueries: [{ propertyName: "mainMenuTrigger", first: true, predicate: ["mainMenuTrigger"], descendants: true }], ngImport: i0, template: "<button type=\"button\" class=\"organizer-btn\" [class.organizer-btn--labeled]=\"!!config.buttonLabel\" [matMenuTriggerFor]=\"viewMenu\" #mainMenuTrigger=\"matMenuTrigger\" [attr.aria-label]=\"config.buttonLabel || 'Personnaliser la vue'\" [attr.title]=\"config.buttonLabel || 'Personnaliser la vue'\">\n <mat-icon *ngIf=\"config.showButtonIcon !== false\" class=\"organizer-btn-icon\">{{ config.buttonIcon || 'settings' }}</mat-icon>\n <span *ngIf=\"config.buttonLabel\" class=\"organizer-btn-label\">{{ config.buttonLabel }}</span>\n</button>\n\n<mat-menu #viewMenu=\"matMenu\" panelClass=\"organizer-menu-panel\">\n\n <!-- Type -->\n <button *ngIf=\"hasChartTypes()\" mat-menu-item [matMenuTriggerFor]=\"chartTypesSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Type</span>\n <span class=\"organizer-menu-value\" *ngIf=\"!hideMenuValues && activeChartTypeLabel()\">{{ activeChartTypeLabel() }}</span>\n </button>\n\n <!-- Champs : mode chart (X + Y) -->\n <button *ngIf=\"hasChartFields()\" mat-menu-item [matMenuTriggerFor]=\"champsSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Champs</span>\n <span class=\"organizer-menu-value\" *ngIf=\"!hideMenuValues && (activeXLabel() || activeYLabel())\">\n {{ activeXLabel() }}<ng-container *ngIf=\"activeXLabel() && activeYLabel()\"> / </ng-container>{{ activeYLabel() }}\n </span>\n </button>\n\n <!-- Champs : mode table (toggle colonnes) -->\n <button *ngIf=\"hasTableFields()\" mat-menu-item [matMenuTriggerFor]=\"fieldsSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Champs</span>\n <span class=\"organizer-menu-value\" *ngIf=\"!hideMenuValues\">{{ fieldsSummary() }}</span>\n </button>\n\n <!-- Grouper par -->\n <button *ngIf=\"config.groups?.length\" mat-menu-item [matMenuTriggerFor]=\"groupsSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Grouper par</span>\n <span class=\"organizer-menu-value\" *ngIf=\"!hideMenuValues && activeGroupByLabel()\">{{ activeGroupByLabel() }}</span>\n </button>\n\n <!-- Filtrer par -->\n <button *ngIf=\"config.slices?.length\" mat-menu-item [matMenuTriggerFor]=\"slicesSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Filtrer par</span>\n <span class=\"organizer-menu-value\" *ngIf=\"!hideMenuValues && activeSlicesLabel()\">{{ activeSlicesLabel() }}</span>\n </button>\n\n <!-- Template -->\n <button *ngIf=\"config.templates?.length\" mat-menu-item [matMenuTriggerFor]=\"templateSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Template</span>\n <span class=\"organizer-menu-value\" *ngIf=\"!hideMenuValues && activeTemplateLabel()\">{{ activeTemplateLabel() }}</span>\n </button>\n\n <!-- Actions du visuel courant -->\n <mat-divider *ngIf=\"hasActions() && hasMenuItemsBeforeActions()\" class=\"organizer-divider\"></mat-divider>\n <button *ngIf=\"hasActions() && hasOtherMenuItems()\" mat-menu-item [matMenuTriggerFor]=\"actionsSubMenu\" class=\"organizer-menu-trigger\">\n <mat-icon *ngIf=\"config.actions?.icon\" class=\"organizer-menu-export-icon\">{{ config.actions?.icon }}</mat-icon>\n <span class=\"organizer-menu-label\">{{ actionsLabel() }}</span>\n </button>\n\n <!-- Actions directes lorsqu'il n'y a aucun autre item principal -->\n <ng-container *ngIf=\"hasActions() && !hasOtherMenuItems()\">\n <button *ngIf=\"isCopyActionVisible()\" mat-menu-item type=\"button\" (click)=\"onCopyVisual()\">\n <mat-icon class=\"organizer-menu-export-icon\">content_copy</mat-icon>\n <span class=\"organizer-menu-label\">Copier</span>\n </button>\n <button *ngIf=\"isFullscreenActionVisible()\" mat-menu-item type=\"button\" (click)=\"onToggleFullscreen()\">\n <mat-icon class=\"organizer-menu-export-icon\">{{ config.isFullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon>\n <span class=\"organizer-menu-label\">{{ config.isFullscreen ? 'Quitter le plein \u00E9cran' : 'Plein \u00E9cran' }}</span>\n </button>\n </ng-container>\n\n <!-- Export direct (mode table) -->\n <mat-divider *ngIf=\"config.showExport\" class=\"organizer-divider\"></mat-divider>\n <button *ngIf=\"config.showExport && !hasExportSubMenu()\" mat-menu-item type=\"button\" class=\"organizer-menu-export\" (click)=\"onExport()\">\n <mat-icon class=\"organizer-menu-export-icon\">download</mat-icon>\n <span class=\"organizer-menu-label\">Exporter</span>\n </button>\n\n <!-- Export graphique (mode chart) \u2014 ouvre un sous-menu -->\n <button *ngIf=\"config.showExport && hasExportSubMenu()\" mat-menu-item [matMenuTriggerFor]=\"exportSubMenu\" class=\"organizer-menu-trigger organizer-menu-export\">\n <mat-icon class=\"organizer-menu-export-icon\">download</mat-icon>\n <span class=\"organizer-menu-label\">Exporter</span>\n </button>\n\n <!-- Pr\u00E9f\u00E9rences -->\n <mat-divider *ngIf=\"config.showPreferences\" class=\"organizer-divider\"></mat-divider>\n <button *ngIf=\"config.showPreferences\" mat-menu-item [matMenuTriggerFor]=\"preferencesSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Pr\u00E9f\u00E9rences</span>\n </button>\n\n <mat-divider *ngIf=\"config.showReset === true\" class=\"organizer-divider\"></mat-divider>\n <button *ngIf=\"config.showReset === true\" mat-menu-item type=\"button\" class=\"organizer-menu-reset\" (click)=\"onReset()\">\n <mat-icon class=\"organizer-menu-export-icon\">restart_alt</mat-icon>\n <span class=\"organizer-menu-label\">R\u00E9initialiser la vue</span>\n </button>\n\n <!-- Switch chart / table -->\n <mat-divider *ngIf=\"hasSwitchView()\" class=\"organizer-divider\"></mat-divider>\n <button *ngIf=\"hasSwitchView()\" mat-menu-item type=\"button\" class=\"organizer-menu-trigger\" (click)=\"onSwitchView()\">\n <mat-icon class=\"organizer-menu-export-icon\">{{ config.switchView?.currentView === 'chart' ? 'table_chart' : 'bar_chart' }}</mat-icon>\n <span class=\"organizer-menu-label\">{{ config.switchView?.currentView === 'chart' ? 'Tableau' : 'Graphique' }}</span>\n </button>\n\n</mat-menu>\n\n<!-- Sous-menu : types de graphique -->\n<mat-menu #chartTypesSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let chartType of config.chartTypes\"\n [class.active]=\"isChartTypeActive(chartType.id)\"\n [disabled]=\"chartType.disabled\"\n (click)=\"onChartTypeSelect(chartType.id)\">\n <span class=\"organizer-submenu-check\">{{ isChartTypeActive(chartType.id) ? '\u2713' : '' }}</span>\n <mat-icon *ngIf=\"chartType.icon\" class=\"organizer-submenu-icon\">{{ chartType.icon }}</mat-icon>\n {{ chartType.label }}\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : actions du visuel -->\n<mat-menu #actionsSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button *ngIf=\"isCopyActionVisible()\" type=\"button\" class=\"organizer-submenu-item\"\n (click)=\"onCopyVisual()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">content_copy</mat-icon>\n Copier\n </button>\n <button *ngIf=\"isFullscreenActionVisible()\" type=\"button\" class=\"organizer-submenu-item\"\n (click)=\"onToggleFullscreen()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">{{ config.isFullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon>\n {{ config.isFullscreen ? 'Quitter le plein \u00E9cran' : 'Plein \u00E9cran' }}\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : Export graphique -->\n<mat-menu #exportSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button *ngIf=\"config.onExportVisual\" type=\"button\" class=\"organizer-submenu-item\"\n (click)=\"onExportVisual()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">image</mat-icon>\n Visuel\n </button>\n <button *ngIf=\"config.onExportData\" type=\"button\" class=\"organizer-submenu-item\"\n (click)=\"onExportData()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">table_chart</mat-icon>\n Donn\u00E9es\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : Champs chart (X + Y) -->\n<mat-menu #champsSubMenu=\"matMenu\" panelClass=\"organizer-champs-panel\">\n <div class=\"organizer-champs-wrapper\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n\n <!-- Bloc X -->\n <div class=\"organizer-champs-block\" *ngIf=\"config.xFields?.length\">\n <span class=\"organizer-champs-block-title\">Axe horizontal \u2014 X</span>\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let xField of config.xFields\"\n [class.active]=\"state?.selectedX === xField.id\"\n [disabled]=\"xField.disabled\"\n (click)=\"onXFieldSelect(xField.id)\">\n <span class=\"organizer-submenu-check\">{{ state?.selectedX === xField.id ? '\u2713' : '' }}</span>\n {{ xField.label }}\n </button>\n </div>\n\n <mat-divider *ngIf=\"config.xFields?.length && config.yFields?.length\" class=\"organizer-champs-divider\"></mat-divider>\n\n <!-- Bloc Y -->\n <div class=\"organizer-champs-block\" *ngIf=\"config.yFields?.length\">\n <span class=\"organizer-champs-block-title\">Axe vertical \u2014 Y</span>\n <ng-container *ngFor=\"let yField of config.yFields\">\n\n <!-- Y simple (Count sans agr\u00E9gat ni stack) -->\n <!-- Y simple (Count ou m\u00E9trique directe) -->\n <button *ngIf=\"!yField.aggregates?.length\"\n type=\"button\" class=\"organizer-submenu-item\"\n [class.active]=\"isYActive(yField.id)\"\n [disabled]=\"yField.disabled\"\n (click)=\"onYFieldSelect(yField.id)\">\n <span class=\"organizer-submenu-check\">{{ isYActive(yField.id) ? '\u2713' : '' }}</span>\n {{ yField.label }}\n </button>\n\n <!-- Y avec agr\u00E9gats \u2192 sous-menu -->\n <button *ngIf=\"yField.aggregates?.length\"\n type=\"button\" class=\"organizer-submenu-item organizer-submenu-item--has-children\"\n [class.active]=\"isYActive(yField.id)\"\n [disabled]=\"yField.disabled\"\n [matMenuTriggerFor]=\"yAggSubMenu\"\n [matMenuTriggerData]=\"{ yField: yField }\"\n (mousedown)=\"$event.stopPropagation()\">\n <span class=\"organizer-submenu-check\">{{ isYActive(yField.id) ? '\u2713' : '' }}</span>\n {{ yField.label }}\n <mat-icon class=\"organizer-submenu-arrow\">chevron_right</mat-icon>\n </button>\n\n </ng-container>\n </div>\n\n </div>\n</mat-menu>\n\n<!-- Sous-menu dynamique : agr\u00E9gats d'un champ Y -->\n<mat-menu #yAggSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <ng-template matMenuContent let-yField=\"yField\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let agg of yField.aggregates\"\n [class.active]=\"isYAggregateActive(yField.id, agg.id)\"\n (click)=\"onYAggregateSelect(yField.id, agg.id)\">\n <span class=\"organizer-submenu-check\">{{ isYAggregateActive(yField.id, agg.id) ? '\u2713' : '' }}</span>\n {{ agg.label }}\n </button>\n </div>\n </ng-template>\n</mat-menu>\n\n<!-- Sous-menu : Champs table (toggle colonnes) -->\n<mat-menu #fieldsSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let field of config.fields\"\n [class.active]=\"isFieldVisible(field.id)\"\n (click)=\"onFieldToggle(field.id, !isFieldVisible(field.id))\">\n <span class=\"organizer-submenu-check\">{{ isFieldVisible(field.id) ? '\u2713' : '' }}</span> <mat-icon *ngIf=\"field.icon\" class=\"organizer-submenu-icon\">{{ field.icon }}</mat-icon> {{ field.label }}\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : Grouper par -->\n<mat-menu #groupsSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let group of config.groups\"\n [class.active]=\"state?.selectedGroupBy === group.id\"\n (click)=\"onGroupBySelect(group.id)\">\n <span class=\"organizer-submenu-check\">{{ state?.selectedGroupBy === group.id ? '\u2713' : '' }}</span> <mat-icon *ngIf=\"group.icon\" class=\"organizer-submenu-icon\">{{ group.icon }}</mat-icon> {{ group.label }}\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : Filtrer par -->\n<mat-menu #slicesSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let slice of config.slices\"\n [class.active]=\"state?.selectedSlices?.includes(slice.id)\"\n (click)=\"onSliceClick(slice.id)\">\n <span class=\"organizer-submenu-check\">{{ state?.selectedSlices?.includes(slice.id) ? '\u2713' : '' }}</span> <mat-icon *ngIf=\"slice.icon\" class=\"organizer-submenu-icon\">{{ slice.icon }}</mat-icon> {{ slice.label }}\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : Template -->\n<mat-menu #templateSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let tmpl of config.templates\"\n [class.active]=\"state?.selectedTemplate === tmpl.id\"\n [title]=\"tmpl.description || ''\"\n (click)=\"onTemplateSelect(tmpl.id)\">\n <span class=\"organizer-submenu-check\">{{ state?.selectedTemplate === tmpl.id ? '\u2713' : '' }}</span>\n <mat-icon *ngIf=\"tmpl.icon\" class=\"organizer-submenu-icon\">{{ tmpl.icon }}</mat-icon>\n {{ tmpl.label }}\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : Pr\u00E9f\u00E9rences -->\n<mat-menu #preferencesSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\" (click)=\"onPreferencesEdit()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">edit</mat-icon>\u00C9diter\n </button>\n <button type=\"button\" class=\"organizer-submenu-item\" (click)=\"onPreferencesSave()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">save</mat-icon>Sauvegarder\n </button>\n <button type=\"button\" class=\"organizer-submenu-item\" [disabled]=\"!config.hasSavedPreferences\" (click)=\"onPreferencesClear()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">delete_outline</mat-icon>R\u00E9initialiser\n </button>\n </div>\n</mat-menu>\n\n", styles: [":host{display:inline-flex;align-items:center;justify-content:center;line-height:0}.organizer-btn{position:relative;display:inline-flex;align-items:center;justify-content:center;gap:8px;height:var(--jqt-organizer-button-height, 34px);width:var(--jqt-organizer-button-width, 34px);min-width:var(--jqt-organizer-button-min-width, 34px);border:1px solid var(--jqt-organizer-button-border-color, #e0e0e0);border-radius:var(--jqt-organizer-button-radius, 8px);background:var(--jqt-organizer-button-background, #fff);padding:0;overflow:hidden;cursor:pointer;box-sizing:border-box}.organizer-btn:hover{border-color:var(--jqt-organizer-button-hover-border-color, #9e9e9e);background:var(--jqt-organizer-button-hover-background, var(--jqt-organizer-button-background, #fff))}.organizer-btn--labeled{width:auto;padding:0 12px}.organizer-btn-label{font-size:13px;font-weight:600;line-height:1;white-space:nowrap}.organizer-btn-icon.mat-icon,.organizer-btn-icon.material-icons{position:static;display:block!important;width:18px!important;height:18px!important;margin:0!important;padding:0!important;font-size:18px!important;line-height:18px!important;text-align:center;vertical-align:middle;transform:none;color:var(--jqt-organizer-button-icon-color, #000000)}.mat-mdc-menu-panel:has(.organizer-menu-label){min-width:170px!important;max-width:320px!important;overflow:visible!important}.mat-mdc-menu-panel:has(.organizer-menu-label) .mat-mdc-menu-content{padding:4px 0!important}.mat-mdc-menu-panel:has(.organizer-menu-label) .organizer-menu-trigger.mat-mdc-menu-item{min-height:42px;padding:0 14px;display:flex;align-items:center;box-sizing:border-box}.mat-mdc-menu-panel:has(.organizer-menu-label) .organizer-menu-trigger.mat-mdc-menu-item .mat-mdc-menu-item-text{display:flex;align-items:center;flex:1 1 auto;min-width:0;max-width:calc(100% - 20px);overflow:hidden;white-space:nowrap}.mat-mdc-menu-panel:has(.organizer-menu-label) .organizer-menu-trigger.mat-mdc-menu-item .organizer-menu-label,.mat-mdc-menu-panel:has(.organizer-menu-label) .organizer-menu-trigger.mat-mdc-menu-item .organizer-menu-value{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mat-mdc-menu-panel:has(.organizer-menu-label) .organizer-menu-trigger.mat-mdc-menu-item .organizer-menu-value{display:block}.organizer-menu-label{font-size:13px;font-weight:500;color:#212121;flex:0 0 auto;white-space:nowrap;padding-right:8px}.organizer-menu-value{font-size:12px;color:#757575;flex:1 1 auto;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-align:right}.organizer-submenu{padding:4px 6px;min-width:180px;box-sizing:border-box}.organizer-submenu .organizer-submenu-item{display:flex;align-items:center;gap:6px;width:100%;padding:7px 8px;border:none;background:transparent;border-radius:6px;font-size:13px;color:#212121;cursor:pointer;text-align:left;box-sizing:border-box;white-space:nowrap}.organizer-submenu .organizer-submenu-item:hover{background:#f5f5f5}.organizer-submenu .organizer-submenu-item.active{font-weight:600}.organizer-submenu .organizer-submenu-item[disabled]{opacity:.38;cursor:default}.organizer-submenu-check{flex-shrink:0;width:16px;text-align:center;font-size:12px;color:#212121}.organizer-submenu-icon{font-size:14px!important;width:14px!important;height:14px!important;color:#212121;margin-right:4px}.organizer-menu-export.mat-mdc-menu-item{min-height:42px;padding:0 14px}.organizer-menu-export-icon{font-size:16px!important;width:16px!important;height:16px!important;color:#424242;margin-right:8px}.organizer-divider{margin:4px 0!important}.organizer-champs-wrapper{padding:4px 6px;min-width:200px;box-sizing:border-box;display:flex;flex-direction:column}.organizer-champs-block{display:flex;flex-direction:column}.organizer-champs-block-title{font-size:10px;font-weight:700;letter-spacing:.6px;text-transform:uppercase;color:#9e9e9e;padding:6px 2px 2px}.organizer-champs-divider{margin:4px 0!important}.organizer-champs-wrapper .organizer-submenu-item{display:flex;align-items:center;gap:6px;width:100%;padding:7px 8px;border:none;background:transparent;border-radius:6px;font-size:13px;font-family:inherit;color:#212121;cursor:pointer;text-align:left;box-sizing:border-box;white-space:nowrap}.organizer-champs-wrapper .organizer-submenu-item:hover:not([disabled]){background:#f5f5f5}.organizer-champs-wrapper .organizer-submenu-item.active{font-weight:600}.organizer-champs-wrapper .organizer-submenu-item[disabled]{opacity:.38;cursor:default}.organizer-champs-wrapper .organizer-submenu-check{flex-shrink:0;width:16px;text-align:center;font-size:12px;color:#212121}.organizer-champs-wrapper .organizer-submenu-item--has-children{padding-right:8px}.organizer-champs-wrapper .organizer-submenu-arrow{font-size:18px!important;width:18px!important;height:18px!important;color:#00000061;margin-left:auto}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i3.MatMenu, selector: "mat-menu", exportAs: ["matMenu"] }, { kind: "component", type: i3.MatMenuItem, selector: "[mat-menu-item]", inputs: ["disabled", "disableRipple", "role"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i3.MatMenuContent, selector: "ng-template[matMenuContent]" }, { kind: "directive", type: i3.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", exportAs: ["matMenuTrigger"] }, { kind: "ngmodule", type: MatDividerModule }, { kind: "component", type: i4.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
|
|
481
514
|
}
|
|
482
515
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: OrganizerButtonComponent, decorators: [{
|
|
483
516
|
type: Component,
|
|
484
|
-
args: [{ standalone: true, imports: [CommonModule, MatButtonModule, MatIconModule, MatMenuModule, MatDividerModule], selector: 'organizer-button', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, template: "<button type=\"button\" class=\"organizer-btn\" [matMenuTriggerFor]=\"viewMenu\" #mainMenuTrigger=\"matMenuTrigger\" [attr.aria-label]=\"'View'\">\n <mat-icon class=\"organizer-btn-icon\">{{ config.buttonIcon || 'settings' }}</mat-icon>\n</button>\n\n<mat-menu #viewMenu=\"matMenu\" panelClass=\"organizer-menu-panel\">\n\n <!-- Champs : mode chart (X + Y) -->\n <button *ngIf=\"hasChartFields()\" mat-menu-item [matMenuTriggerFor]=\"champsSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Champs</span>\n <span class=\"organizer-menu-value\" *ngIf=\"!hideMenuValues && (activeXLabel() || activeYLabel())\">\n {{ activeXLabel() }}<ng-container *ngIf=\"activeXLabel() && activeYLabel()\"> / </ng-container>{{ activeYLabel() }}\n </span>\n </button>\n\n <!-- Champs : mode table (toggle colonnes) -->\n <button *ngIf=\"hasTableFields()\" mat-menu-item [matMenuTriggerFor]=\"fieldsSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Champs</span>\n <span class=\"organizer-menu-value\" *ngIf=\"!hideMenuValues\">{{ fieldsSummary() }}</span>\n </button>\n\n <!-- Grouper par -->\n <button *ngIf=\"config.groups?.length\" mat-menu-item [matMenuTriggerFor]=\"groupsSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Grouper par</span>\n <span class=\"organizer-menu-value\" *ngIf=\"!hideMenuValues && activeGroupByLabel()\">{{ activeGroupByLabel() }}</span>\n </button>\n\n <!-- Filtrer par -->\n <button *ngIf=\"config.slices?.length\" mat-menu-item [matMenuTriggerFor]=\"slicesSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Filtrer par</span>\n <span class=\"organizer-menu-value\" *ngIf=\"!hideMenuValues && activeSlicesLabel()\">{{ activeSlicesLabel() }}</span>\n </button>\n\n <!-- Template -->\n <button *ngIf=\"config.templates?.length\" mat-menu-item [matMenuTriggerFor]=\"templateSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Template</span>\n <span class=\"organizer-menu-value\" *ngIf=\"!hideMenuValues && activeTemplateLabel()\">{{ activeTemplateLabel() }}</span>\n </button>\n\n <!-- Actions du visuel courant -->\n <mat-divider *ngIf=\"hasActions() && hasMenuItemsBeforeActions()\" class=\"organizer-divider\"></mat-divider>\n <button *ngIf=\"hasActions() && hasOtherMenuItems()\" mat-menu-item [matMenuTriggerFor]=\"actionsSubMenu\" class=\"organizer-menu-trigger\">\n <mat-icon *ngIf=\"config.actions?.icon\" class=\"organizer-menu-export-icon\">{{ config.actions?.icon }}</mat-icon>\n <span class=\"organizer-menu-label\">{{ actionsLabel() }}</span>\n </button>\n\n <!-- Actions directes lorsqu'il n'y a aucun autre item principal -->\n <ng-container *ngIf=\"hasActions() && !hasOtherMenuItems()\">\n <button *ngIf=\"isCopyActionVisible()\" mat-menu-item type=\"button\" (click)=\"onCopyVisual()\">\n <mat-icon class=\"organizer-menu-export-icon\">content_copy</mat-icon>\n <span class=\"organizer-menu-label\">Copier</span>\n </button>\n <button *ngIf=\"isFullscreenActionVisible()\" mat-menu-item type=\"button\" (click)=\"onToggleFullscreen()\">\n <mat-icon class=\"organizer-menu-export-icon\">{{ config.isFullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon>\n <span class=\"organizer-menu-label\">{{ config.isFullscreen ? 'Quitter le plein \u00E9cran' : 'Plein \u00E9cran' }}</span>\n </button>\n </ng-container>\n\n <!-- Export direct (mode table) -->\n <mat-divider *ngIf=\"config.showExport\" class=\"organizer-divider\"></mat-divider>\n <button *ngIf=\"config.showExport && !hasExportSubMenu()\" mat-menu-item type=\"button\" class=\"organizer-menu-export\" (click)=\"onExport()\">\n <mat-icon class=\"organizer-menu-export-icon\">download</mat-icon>\n <span class=\"organizer-menu-label\">Exporter</span>\n </button>\n\n <!-- Export graphique (mode chart) \u2014 ouvre un sous-menu -->\n <button *ngIf=\"config.showExport && hasExportSubMenu()\" mat-menu-item [matMenuTriggerFor]=\"exportSubMenu\" class=\"organizer-menu-trigger organizer-menu-export\">\n <mat-icon class=\"organizer-menu-export-icon\">download</mat-icon>\n <span class=\"organizer-menu-label\">Exporter</span>\n </button>\n\n <!-- Pr\u00E9f\u00E9rences -->\n <mat-divider *ngIf=\"config.showPreferences\" class=\"organizer-divider\"></mat-divider>\n <button *ngIf=\"config.showPreferences\" mat-menu-item [matMenuTriggerFor]=\"preferencesSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Pr\u00E9f\u00E9rences</span>\n </button>\n\n <!-- Switch chart / table -->\n <mat-divider *ngIf=\"hasSwitchView()\" class=\"organizer-divider\"></mat-divider>\n <button *ngIf=\"hasSwitchView()\" mat-menu-item type=\"button\" class=\"organizer-menu-trigger\" (click)=\"onSwitchView()\">\n <mat-icon class=\"organizer-menu-export-icon\">{{ config.switchView?.currentView === 'chart' ? 'table_chart' : 'bar_chart' }}</mat-icon>\n <span class=\"organizer-menu-label\">{{ config.switchView?.currentView === 'chart' ? 'Tableau' : 'Graphique' }}</span>\n </button>\n\n</mat-menu>\n\n<!-- Sous-menu : actions du visuel -->\n<mat-menu #actionsSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button *ngIf=\"isCopyActionVisible()\" type=\"button\" class=\"organizer-submenu-item\"\n (mousedown)=\"onCopyVisual()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">content_copy</mat-icon>\n Copier\n </button>\n <button *ngIf=\"isFullscreenActionVisible()\" type=\"button\" class=\"organizer-submenu-item\"\n (mousedown)=\"onToggleFullscreen()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">{{ config.isFullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon>\n {{ config.isFullscreen ? 'Quitter le plein \u00E9cran' : 'Plein \u00E9cran' }}\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : Export graphique -->\n<mat-menu #exportSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button *ngIf=\"config.onExportVisual\" type=\"button\" class=\"organizer-submenu-item\"\n (mousedown)=\"onExportVisual()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">image</mat-icon>\n Visuel\n </button>\n <button *ngIf=\"config.onExportData\" type=\"button\" class=\"organizer-submenu-item\"\n (mousedown)=\"onExportData()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">table_chart</mat-icon>\n Donn\u00E9es\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : Champs chart (X + Y) -->\n<mat-menu #champsSubMenu=\"matMenu\" panelClass=\"organizer-champs-panel\">\n <div class=\"organizer-champs-wrapper\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n\n <!-- Bloc X -->\n <div class=\"organizer-champs-block\" *ngIf=\"config.xFields?.length\">\n <span class=\"organizer-champs-block-title\">Axe horizontal \u2014 X</span>\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let xField of config.xFields\"\n [class.active]=\"state?.selectedX === xField.id\"\n [disabled]=\"xField.disabled\"\n (mousedown)=\"onXFieldSelect(xField.id)\">\n <span class=\"organizer-submenu-check\">{{ state?.selectedX === xField.id ? '\u2713' : '' }}</span>\n {{ xField.label }}\n </button>\n </div>\n\n <mat-divider *ngIf=\"config.xFields?.length && config.yFields?.length\" class=\"organizer-champs-divider\"></mat-divider>\n\n <!-- Bloc Y -->\n <div class=\"organizer-champs-block\" *ngIf=\"config.yFields?.length\">\n <span class=\"organizer-champs-block-title\">Axe vertical \u2014 Y</span>\n <ng-container *ngFor=\"let yField of config.yFields\">\n\n <!-- Y simple (Count sans agr\u00E9gat ni stack) -->\n <!-- Y simple (Count ou m\u00E9trique directe) -->\n <button *ngIf=\"!yField.aggregates?.length\"\n type=\"button\" class=\"organizer-submenu-item\"\n [class.active]=\"isYActive(yField.id)\"\n [disabled]=\"yField.disabled\"\n (mousedown)=\"onYFieldSelect(yField.id)\">\n <span class=\"organizer-submenu-check\">{{ isYActive(yField.id) ? '\u2713' : '' }}</span>\n {{ yField.label }}\n </button>\n\n <!-- Y avec agr\u00E9gats \u2192 sous-menu -->\n <button *ngIf=\"yField.aggregates?.length\"\n type=\"button\" class=\"organizer-submenu-item organizer-submenu-item--has-children\"\n [class.active]=\"isYActive(yField.id)\"\n [disabled]=\"yField.disabled\"\n [matMenuTriggerFor]=\"yAggSubMenu\"\n [matMenuTriggerData]=\"{ yField: yField }\"\n (mousedown)=\"$event.stopPropagation()\">\n <span class=\"organizer-submenu-check\">{{ isYActive(yField.id) ? '\u2713' : '' }}</span>\n {{ yField.label }}\n <mat-icon class=\"organizer-submenu-arrow\">chevron_right</mat-icon>\n </button>\n\n </ng-container>\n </div>\n\n </div>\n</mat-menu>\n\n<!-- Sous-menu dynamique : agr\u00E9gats d'un champ Y -->\n<mat-menu #yAggSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <ng-template matMenuContent let-yField=\"yField\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let agg of yField.aggregates\"\n [class.active]=\"isYAggregateActive(yField.id, agg.id)\"\n (mousedown)=\"onYAggregateSelect(yField.id, agg.id)\">\n <span class=\"organizer-submenu-check\">{{ isYAggregateActive(yField.id, agg.id) ? '\u2713' : '' }}</span>\n {{ agg.label }}\n </button>\n </div>\n </ng-template>\n</mat-menu>\n\n<!-- Sous-menu : Champs table (toggle colonnes) -->\n<mat-menu #fieldsSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let field of config.fields\"\n [class.active]=\"isFieldVisible(field.id)\"\n (mousedown)=\"onFieldToggle(field.id, !isFieldVisible(field.id))\">\n <span class=\"organizer-submenu-check\">{{ isFieldVisible(field.id) ? '\u2713' : '' }}</span> <mat-icon *ngIf=\"field.icon\" class=\"organizer-submenu-icon\">{{ field.icon }}</mat-icon> {{ field.label }}\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : Grouper par -->\n<mat-menu #groupsSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let group of config.groups\"\n [class.active]=\"state?.selectedGroupBy === group.id\"\n (mousedown)=\"onGroupBySelect(group.id)\">\n <span class=\"organizer-submenu-check\">{{ state?.selectedGroupBy === group.id ? '\u2713' : '' }}</span> <mat-icon *ngIf=\"group.icon\" class=\"organizer-submenu-icon\">{{ group.icon }}</mat-icon> {{ group.label }}\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : Filtrer par -->\n<mat-menu #slicesSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let slice of config.slices\"\n [class.active]=\"state?.selectedSlices?.includes(slice.id)\"\n (mousedown)=\"onSliceClick(slice.id)\">\n <span class=\"organizer-submenu-check\">{{ state?.selectedSlices?.includes(slice.id) ? '\u2713' : '' }}</span> <mat-icon *ngIf=\"slice.icon\" class=\"organizer-submenu-icon\">{{ slice.icon }}</mat-icon> {{ slice.label }}\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : Template -->\n<mat-menu #templateSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let tmpl of config.templates\"\n [class.active]=\"state?.selectedTemplate === tmpl.id\"\n [title]=\"tmpl.description || ''\"\n (mousedown)=\"onTemplateSelect(tmpl.id)\">\n <span class=\"organizer-submenu-check\">{{ state?.selectedTemplate === tmpl.id ? '\u2713' : '' }}</span>\n <mat-icon *ngIf=\"tmpl.icon\" class=\"organizer-submenu-icon\">{{ tmpl.icon }}</mat-icon>\n {{ tmpl.label }}\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : Pr\u00E9f\u00E9rences -->\n<mat-menu #preferencesSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\" (mousedown)=\"onPreferencesEdit()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">edit</mat-icon>\u00C9diter\n </button>\n <button type=\"button\" class=\"organizer-submenu-item\" (mousedown)=\"onPreferencesSave()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">save</mat-icon>Sauvegarder\n </button>\n <button type=\"button\" class=\"organizer-submenu-item\" [disabled]=\"!config.hasSavedPreferences\" (mousedown)=\"onPreferencesClear()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">delete_outline</mat-icon>R\u00E9initialiser\n </button>\n </div>\n</mat-menu>\n\n", styles: [":host{display:inline-flex;align-items:center;justify-content:center;line-height:0}.organizer-btn{position:relative;display:block;height:34px;width:34px;border:1px solid #e0e0e0;border-radius:8px;background:#fff;padding:0;overflow:hidden;cursor:pointer;box-sizing:border-box}.organizer-btn:hover{border-color:#9e9e9e}.organizer-btn-icon.mat-icon,.organizer-btn-icon.material-icons{position:absolute;inset:0;display:block!important;width:18px!important;height:18px!important;margin:auto!important;padding:0!important;font-size:18px!important;line-height:18px!important;text-align:center;vertical-align:middle;transform:none;color:#000}.mat-mdc-menu-panel:has(.organizer-menu-label){min-width:170px!important;max-width:320px!important;overflow:visible!important}.mat-mdc-menu-panel:has(.organizer-menu-label) .mat-mdc-menu-content{padding:4px 0!important}.mat-mdc-menu-panel:has(.organizer-menu-label) .organizer-menu-trigger.mat-mdc-menu-item{min-height:42px;padding:0 14px;display:flex;align-items:center;box-sizing:border-box}.mat-mdc-menu-panel:has(.organizer-menu-label) .organizer-menu-trigger.mat-mdc-menu-item .mdc-list-item__primary-text{display:flex;align-items:center;width:100%;overflow:hidden}.organizer-menu-label{font-size:13px;font-weight:500;color:#212121;flex:0 0 auto;white-space:nowrap;padding-right:8px}.organizer-menu-value{font-size:12px;color:#757575;flex:1 1 auto;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-align:right}.organizer-submenu{padding:4px 6px;min-width:180px;box-sizing:border-box}.organizer-submenu .organizer-submenu-item{display:flex;align-items:center;gap:6px;width:100%;padding:7px 8px;border:none;background:transparent;border-radius:6px;font-size:13px;color:#212121;cursor:pointer;text-align:left;box-sizing:border-box;white-space:nowrap}.organizer-submenu .organizer-submenu-item:hover{background:#f5f5f5}.organizer-submenu .organizer-submenu-item.active{font-weight:600}.organizer-submenu-check{flex-shrink:0;width:16px;text-align:center;font-size:12px;color:#212121}.organizer-submenu-icon{font-size:14px!important;width:14px!important;height:14px!important;color:#212121;margin-right:4px}.organizer-menu-export.mat-mdc-menu-item{min-height:42px;padding:0 14px}.organizer-menu-export-icon{font-size:16px!important;width:16px!important;height:16px!important;color:#424242;margin-right:8px}.organizer-divider{margin:4px 0!important}.organizer-champs-wrapper{padding:4px 6px;min-width:200px;box-sizing:border-box;display:flex;flex-direction:column}.organizer-champs-block{display:flex;flex-direction:column}.organizer-champs-block-title{font-size:10px;font-weight:700;letter-spacing:.6px;text-transform:uppercase;color:#9e9e9e;padding:6px 2px 2px}.organizer-champs-divider{margin:4px 0!important}.organizer-champs-wrapper .organizer-submenu-item{display:flex;align-items:center;gap:6px;width:100%;padding:7px 8px;border:none;background:transparent;border-radius:6px;font-size:13px;font-family:inherit;color:#212121;cursor:pointer;text-align:left;box-sizing:border-box;white-space:nowrap}.organizer-champs-wrapper .organizer-submenu-item:hover:not([disabled]){background:#f5f5f5}.organizer-champs-wrapper .organizer-submenu-item.active{font-weight:600}.organizer-champs-wrapper .organizer-submenu-item[disabled]{opacity:.38;cursor:default}.organizer-champs-wrapper .organizer-submenu-check{flex-shrink:0;width:16px;text-align:center;font-size:12px;color:#212121}.organizer-champs-wrapper .organizer-submenu-item--has-children{padding-right:8px}.organizer-champs-wrapper .organizer-submenu-arrow{font-size:18px!important;width:18px!important;height:18px!important;color:#00000061;margin-left:auto}\n"] }]
|
|
517
|
+
args: [{ standalone: true, imports: [CommonModule, MatButtonModule, MatIconModule, MatMenuModule, MatDividerModule], selector: 'organizer-button', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, template: "<button type=\"button\" class=\"organizer-btn\" [class.organizer-btn--labeled]=\"!!config.buttonLabel\" [matMenuTriggerFor]=\"viewMenu\" #mainMenuTrigger=\"matMenuTrigger\" [attr.aria-label]=\"config.buttonLabel || 'Personnaliser la vue'\" [attr.title]=\"config.buttonLabel || 'Personnaliser la vue'\">\n <mat-icon *ngIf=\"config.showButtonIcon !== false\" class=\"organizer-btn-icon\">{{ config.buttonIcon || 'settings' }}</mat-icon>\n <span *ngIf=\"config.buttonLabel\" class=\"organizer-btn-label\">{{ config.buttonLabel }}</span>\n</button>\n\n<mat-menu #viewMenu=\"matMenu\" panelClass=\"organizer-menu-panel\">\n\n <!-- Type -->\n <button *ngIf=\"hasChartTypes()\" mat-menu-item [matMenuTriggerFor]=\"chartTypesSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Type</span>\n <span class=\"organizer-menu-value\" *ngIf=\"!hideMenuValues && activeChartTypeLabel()\">{{ activeChartTypeLabel() }}</span>\n </button>\n\n <!-- Champs : mode chart (X + Y) -->\n <button *ngIf=\"hasChartFields()\" mat-menu-item [matMenuTriggerFor]=\"champsSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Champs</span>\n <span class=\"organizer-menu-value\" *ngIf=\"!hideMenuValues && (activeXLabel() || activeYLabel())\">\n {{ activeXLabel() }}<ng-container *ngIf=\"activeXLabel() && activeYLabel()\"> / </ng-container>{{ activeYLabel() }}\n </span>\n </button>\n\n <!-- Champs : mode table (toggle colonnes) -->\n <button *ngIf=\"hasTableFields()\" mat-menu-item [matMenuTriggerFor]=\"fieldsSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Champs</span>\n <span class=\"organizer-menu-value\" *ngIf=\"!hideMenuValues\">{{ fieldsSummary() }}</span>\n </button>\n\n <!-- Grouper par -->\n <button *ngIf=\"config.groups?.length\" mat-menu-item [matMenuTriggerFor]=\"groupsSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Grouper par</span>\n <span class=\"organizer-menu-value\" *ngIf=\"!hideMenuValues && activeGroupByLabel()\">{{ activeGroupByLabel() }}</span>\n </button>\n\n <!-- Filtrer par -->\n <button *ngIf=\"config.slices?.length\" mat-menu-item [matMenuTriggerFor]=\"slicesSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Filtrer par</span>\n <span class=\"organizer-menu-value\" *ngIf=\"!hideMenuValues && activeSlicesLabel()\">{{ activeSlicesLabel() }}</span>\n </button>\n\n <!-- Template -->\n <button *ngIf=\"config.templates?.length\" mat-menu-item [matMenuTriggerFor]=\"templateSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Template</span>\n <span class=\"organizer-menu-value\" *ngIf=\"!hideMenuValues && activeTemplateLabel()\">{{ activeTemplateLabel() }}</span>\n </button>\n\n <!-- Actions du visuel courant -->\n <mat-divider *ngIf=\"hasActions() && hasMenuItemsBeforeActions()\" class=\"organizer-divider\"></mat-divider>\n <button *ngIf=\"hasActions() && hasOtherMenuItems()\" mat-menu-item [matMenuTriggerFor]=\"actionsSubMenu\" class=\"organizer-menu-trigger\">\n <mat-icon *ngIf=\"config.actions?.icon\" class=\"organizer-menu-export-icon\">{{ config.actions?.icon }}</mat-icon>\n <span class=\"organizer-menu-label\">{{ actionsLabel() }}</span>\n </button>\n\n <!-- Actions directes lorsqu'il n'y a aucun autre item principal -->\n <ng-container *ngIf=\"hasActions() && !hasOtherMenuItems()\">\n <button *ngIf=\"isCopyActionVisible()\" mat-menu-item type=\"button\" (click)=\"onCopyVisual()\">\n <mat-icon class=\"organizer-menu-export-icon\">content_copy</mat-icon>\n <span class=\"organizer-menu-label\">Copier</span>\n </button>\n <button *ngIf=\"isFullscreenActionVisible()\" mat-menu-item type=\"button\" (click)=\"onToggleFullscreen()\">\n <mat-icon class=\"organizer-menu-export-icon\">{{ config.isFullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon>\n <span class=\"organizer-menu-label\">{{ config.isFullscreen ? 'Quitter le plein \u00E9cran' : 'Plein \u00E9cran' }}</span>\n </button>\n </ng-container>\n\n <!-- Export direct (mode table) -->\n <mat-divider *ngIf=\"config.showExport\" class=\"organizer-divider\"></mat-divider>\n <button *ngIf=\"config.showExport && !hasExportSubMenu()\" mat-menu-item type=\"button\" class=\"organizer-menu-export\" (click)=\"onExport()\">\n <mat-icon class=\"organizer-menu-export-icon\">download</mat-icon>\n <span class=\"organizer-menu-label\">Exporter</span>\n </button>\n\n <!-- Export graphique (mode chart) \u2014 ouvre un sous-menu -->\n <button *ngIf=\"config.showExport && hasExportSubMenu()\" mat-menu-item [matMenuTriggerFor]=\"exportSubMenu\" class=\"organizer-menu-trigger organizer-menu-export\">\n <mat-icon class=\"organizer-menu-export-icon\">download</mat-icon>\n <span class=\"organizer-menu-label\">Exporter</span>\n </button>\n\n <!-- Pr\u00E9f\u00E9rences -->\n <mat-divider *ngIf=\"config.showPreferences\" class=\"organizer-divider\"></mat-divider>\n <button *ngIf=\"config.showPreferences\" mat-menu-item [matMenuTriggerFor]=\"preferencesSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Pr\u00E9f\u00E9rences</span>\n </button>\n\n <mat-divider *ngIf=\"config.showReset === true\" class=\"organizer-divider\"></mat-divider>\n <button *ngIf=\"config.showReset === true\" mat-menu-item type=\"button\" class=\"organizer-menu-reset\" (click)=\"onReset()\">\n <mat-icon class=\"organizer-menu-export-icon\">restart_alt</mat-icon>\n <span class=\"organizer-menu-label\">R\u00E9initialiser la vue</span>\n </button>\n\n <!-- Switch chart / table -->\n <mat-divider *ngIf=\"hasSwitchView()\" class=\"organizer-divider\"></mat-divider>\n <button *ngIf=\"hasSwitchView()\" mat-menu-item type=\"button\" class=\"organizer-menu-trigger\" (click)=\"onSwitchView()\">\n <mat-icon class=\"organizer-menu-export-icon\">{{ config.switchView?.currentView === 'chart' ? 'table_chart' : 'bar_chart' }}</mat-icon>\n <span class=\"organizer-menu-label\">{{ config.switchView?.currentView === 'chart' ? 'Tableau' : 'Graphique' }}</span>\n </button>\n\n</mat-menu>\n\n<!-- Sous-menu : types de graphique -->\n<mat-menu #chartTypesSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let chartType of config.chartTypes\"\n [class.active]=\"isChartTypeActive(chartType.id)\"\n [disabled]=\"chartType.disabled\"\n (click)=\"onChartTypeSelect(chartType.id)\">\n <span class=\"organizer-submenu-check\">{{ isChartTypeActive(chartType.id) ? '\u2713' : '' }}</span>\n <mat-icon *ngIf=\"chartType.icon\" class=\"organizer-submenu-icon\">{{ chartType.icon }}</mat-icon>\n {{ chartType.label }}\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : actions du visuel -->\n<mat-menu #actionsSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button *ngIf=\"isCopyActionVisible()\" type=\"button\" class=\"organizer-submenu-item\"\n (click)=\"onCopyVisual()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">content_copy</mat-icon>\n Copier\n </button>\n <button *ngIf=\"isFullscreenActionVisible()\" type=\"button\" class=\"organizer-submenu-item\"\n (click)=\"onToggleFullscreen()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">{{ config.isFullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon>\n {{ config.isFullscreen ? 'Quitter le plein \u00E9cran' : 'Plein \u00E9cran' }}\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : Export graphique -->\n<mat-menu #exportSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button *ngIf=\"config.onExportVisual\" type=\"button\" class=\"organizer-submenu-item\"\n (click)=\"onExportVisual()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">image</mat-icon>\n Visuel\n </button>\n <button *ngIf=\"config.onExportData\" type=\"button\" class=\"organizer-submenu-item\"\n (click)=\"onExportData()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">table_chart</mat-icon>\n Donn\u00E9es\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : Champs chart (X + Y) -->\n<mat-menu #champsSubMenu=\"matMenu\" panelClass=\"organizer-champs-panel\">\n <div class=\"organizer-champs-wrapper\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n\n <!-- Bloc X -->\n <div class=\"organizer-champs-block\" *ngIf=\"config.xFields?.length\">\n <span class=\"organizer-champs-block-title\">Axe horizontal \u2014 X</span>\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let xField of config.xFields\"\n [class.active]=\"state?.selectedX === xField.id\"\n [disabled]=\"xField.disabled\"\n (click)=\"onXFieldSelect(xField.id)\">\n <span class=\"organizer-submenu-check\">{{ state?.selectedX === xField.id ? '\u2713' : '' }}</span>\n {{ xField.label }}\n </button>\n </div>\n\n <mat-divider *ngIf=\"config.xFields?.length && config.yFields?.length\" class=\"organizer-champs-divider\"></mat-divider>\n\n <!-- Bloc Y -->\n <div class=\"organizer-champs-block\" *ngIf=\"config.yFields?.length\">\n <span class=\"organizer-champs-block-title\">Axe vertical \u2014 Y</span>\n <ng-container *ngFor=\"let yField of config.yFields\">\n\n <!-- Y simple (Count sans agr\u00E9gat ni stack) -->\n <!-- Y simple (Count ou m\u00E9trique directe) -->\n <button *ngIf=\"!yField.aggregates?.length\"\n type=\"button\" class=\"organizer-submenu-item\"\n [class.active]=\"isYActive(yField.id)\"\n [disabled]=\"yField.disabled\"\n (click)=\"onYFieldSelect(yField.id)\">\n <span class=\"organizer-submenu-check\">{{ isYActive(yField.id) ? '\u2713' : '' }}</span>\n {{ yField.label }}\n </button>\n\n <!-- Y avec agr\u00E9gats \u2192 sous-menu -->\n <button *ngIf=\"yField.aggregates?.length\"\n type=\"button\" class=\"organizer-submenu-item organizer-submenu-item--has-children\"\n [class.active]=\"isYActive(yField.id)\"\n [disabled]=\"yField.disabled\"\n [matMenuTriggerFor]=\"yAggSubMenu\"\n [matMenuTriggerData]=\"{ yField: yField }\"\n (mousedown)=\"$event.stopPropagation()\">\n <span class=\"organizer-submenu-check\">{{ isYActive(yField.id) ? '\u2713' : '' }}</span>\n {{ yField.label }}\n <mat-icon class=\"organizer-submenu-arrow\">chevron_right</mat-icon>\n </button>\n\n </ng-container>\n </div>\n\n </div>\n</mat-menu>\n\n<!-- Sous-menu dynamique : agr\u00E9gats d'un champ Y -->\n<mat-menu #yAggSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <ng-template matMenuContent let-yField=\"yField\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let agg of yField.aggregates\"\n [class.active]=\"isYAggregateActive(yField.id, agg.id)\"\n (click)=\"onYAggregateSelect(yField.id, agg.id)\">\n <span class=\"organizer-submenu-check\">{{ isYAggregateActive(yField.id, agg.id) ? '\u2713' : '' }}</span>\n {{ agg.label }}\n </button>\n </div>\n </ng-template>\n</mat-menu>\n\n<!-- Sous-menu : Champs table (toggle colonnes) -->\n<mat-menu #fieldsSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let field of config.fields\"\n [class.active]=\"isFieldVisible(field.id)\"\n (click)=\"onFieldToggle(field.id, !isFieldVisible(field.id))\">\n <span class=\"organizer-submenu-check\">{{ isFieldVisible(field.id) ? '\u2713' : '' }}</span> <mat-icon *ngIf=\"field.icon\" class=\"organizer-submenu-icon\">{{ field.icon }}</mat-icon> {{ field.label }}\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : Grouper par -->\n<mat-menu #groupsSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let group of config.groups\"\n [class.active]=\"state?.selectedGroupBy === group.id\"\n (click)=\"onGroupBySelect(group.id)\">\n <span class=\"organizer-submenu-check\">{{ state?.selectedGroupBy === group.id ? '\u2713' : '' }}</span> <mat-icon *ngIf=\"group.icon\" class=\"organizer-submenu-icon\">{{ group.icon }}</mat-icon> {{ group.label }}\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : Filtrer par -->\n<mat-menu #slicesSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let slice of config.slices\"\n [class.active]=\"state?.selectedSlices?.includes(slice.id)\"\n (click)=\"onSliceClick(slice.id)\">\n <span class=\"organizer-submenu-check\">{{ state?.selectedSlices?.includes(slice.id) ? '\u2713' : '' }}</span> <mat-icon *ngIf=\"slice.icon\" class=\"organizer-submenu-icon\">{{ slice.icon }}</mat-icon> {{ slice.label }}\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : Template -->\n<mat-menu #templateSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let tmpl of config.templates\"\n [class.active]=\"state?.selectedTemplate === tmpl.id\"\n [title]=\"tmpl.description || ''\"\n (click)=\"onTemplateSelect(tmpl.id)\">\n <span class=\"organizer-submenu-check\">{{ state?.selectedTemplate === tmpl.id ? '\u2713' : '' }}</span>\n <mat-icon *ngIf=\"tmpl.icon\" class=\"organizer-submenu-icon\">{{ tmpl.icon }}</mat-icon>\n {{ tmpl.label }}\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : Pr\u00E9f\u00E9rences -->\n<mat-menu #preferencesSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\" (click)=\"onPreferencesEdit()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">edit</mat-icon>\u00C9diter\n </button>\n <button type=\"button\" class=\"organizer-submenu-item\" (click)=\"onPreferencesSave()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">save</mat-icon>Sauvegarder\n </button>\n <button type=\"button\" class=\"organizer-submenu-item\" [disabled]=\"!config.hasSavedPreferences\" (click)=\"onPreferencesClear()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">delete_outline</mat-icon>R\u00E9initialiser\n </button>\n </div>\n</mat-menu>\n\n", styles: [":host{display:inline-flex;align-items:center;justify-content:center;line-height:0}.organizer-btn{position:relative;display:inline-flex;align-items:center;justify-content:center;gap:8px;height:var(--jqt-organizer-button-height, 34px);width:var(--jqt-organizer-button-width, 34px);min-width:var(--jqt-organizer-button-min-width, 34px);border:1px solid var(--jqt-organizer-button-border-color, #e0e0e0);border-radius:var(--jqt-organizer-button-radius, 8px);background:var(--jqt-organizer-button-background, #fff);padding:0;overflow:hidden;cursor:pointer;box-sizing:border-box}.organizer-btn:hover{border-color:var(--jqt-organizer-button-hover-border-color, #9e9e9e);background:var(--jqt-organizer-button-hover-background, var(--jqt-organizer-button-background, #fff))}.organizer-btn--labeled{width:auto;padding:0 12px}.organizer-btn-label{font-size:13px;font-weight:600;line-height:1;white-space:nowrap}.organizer-btn-icon.mat-icon,.organizer-btn-icon.material-icons{position:static;display:block!important;width:18px!important;height:18px!important;margin:0!important;padding:0!important;font-size:18px!important;line-height:18px!important;text-align:center;vertical-align:middle;transform:none;color:var(--jqt-organizer-button-icon-color, #000000)}.mat-mdc-menu-panel:has(.organizer-menu-label){min-width:170px!important;max-width:320px!important;overflow:visible!important}.mat-mdc-menu-panel:has(.organizer-menu-label) .mat-mdc-menu-content{padding:4px 0!important}.mat-mdc-menu-panel:has(.organizer-menu-label) .organizer-menu-trigger.mat-mdc-menu-item{min-height:42px;padding:0 14px;display:flex;align-items:center;box-sizing:border-box}.mat-mdc-menu-panel:has(.organizer-menu-label) .organizer-menu-trigger.mat-mdc-menu-item .mat-mdc-menu-item-text{display:flex;align-items:center;flex:1 1 auto;min-width:0;max-width:calc(100% - 20px);overflow:hidden;white-space:nowrap}.mat-mdc-menu-panel:has(.organizer-menu-label) .organizer-menu-trigger.mat-mdc-menu-item .organizer-menu-label,.mat-mdc-menu-panel:has(.organizer-menu-label) .organizer-menu-trigger.mat-mdc-menu-item .organizer-menu-value{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mat-mdc-menu-panel:has(.organizer-menu-label) .organizer-menu-trigger.mat-mdc-menu-item .organizer-menu-value{display:block}.organizer-menu-label{font-size:13px;font-weight:500;color:#212121;flex:0 0 auto;white-space:nowrap;padding-right:8px}.organizer-menu-value{font-size:12px;color:#757575;flex:1 1 auto;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-align:right}.organizer-submenu{padding:4px 6px;min-width:180px;box-sizing:border-box}.organizer-submenu .organizer-submenu-item{display:flex;align-items:center;gap:6px;width:100%;padding:7px 8px;border:none;background:transparent;border-radius:6px;font-size:13px;color:#212121;cursor:pointer;text-align:left;box-sizing:border-box;white-space:nowrap}.organizer-submenu .organizer-submenu-item:hover{background:#f5f5f5}.organizer-submenu .organizer-submenu-item.active{font-weight:600}.organizer-submenu .organizer-submenu-item[disabled]{opacity:.38;cursor:default}.organizer-submenu-check{flex-shrink:0;width:16px;text-align:center;font-size:12px;color:#212121}.organizer-submenu-icon{font-size:14px!important;width:14px!important;height:14px!important;color:#212121;margin-right:4px}.organizer-menu-export.mat-mdc-menu-item{min-height:42px;padding:0 14px}.organizer-menu-export-icon{font-size:16px!important;width:16px!important;height:16px!important;color:#424242;margin-right:8px}.organizer-divider{margin:4px 0!important}.organizer-champs-wrapper{padding:4px 6px;min-width:200px;box-sizing:border-box;display:flex;flex-direction:column}.organizer-champs-block{display:flex;flex-direction:column}.organizer-champs-block-title{font-size:10px;font-weight:700;letter-spacing:.6px;text-transform:uppercase;color:#9e9e9e;padding:6px 2px 2px}.organizer-champs-divider{margin:4px 0!important}.organizer-champs-wrapper .organizer-submenu-item{display:flex;align-items:center;gap:6px;width:100%;padding:7px 8px;border:none;background:transparent;border-radius:6px;font-size:13px;font-family:inherit;color:#212121;cursor:pointer;text-align:left;box-sizing:border-box;white-space:nowrap}.organizer-champs-wrapper .organizer-submenu-item:hover:not([disabled]){background:#f5f5f5}.organizer-champs-wrapper .organizer-submenu-item.active{font-weight:600}.organizer-champs-wrapper .organizer-submenu-item[disabled]{opacity:.38;cursor:default}.organizer-champs-wrapper .organizer-submenu-check{flex-shrink:0;width:16px;text-align:center;font-size:12px;color:#212121}.organizer-champs-wrapper .organizer-submenu-item--has-children{padding-right:8px}.organizer-champs-wrapper .organizer-submenu-arrow{font-size:18px!important;width:18px!important;height:18px!important;color:#00000061;margin-left:auto}\n"] }]
|
|
485
518
|
}], ctorParameters: function () { return [{ type: i0.ChangeDetectorRef }]; }, propDecorators: { config: [{
|
|
486
519
|
type: Input
|
|
487
520
|
}], state: [{
|
|
@@ -511,7 +544,6 @@ class SlicePanelComponent {
|
|
|
511
544
|
this.sliceConfigs = [];
|
|
512
545
|
this.columns = [];
|
|
513
546
|
this.data = [];
|
|
514
|
-
this.lazyData = new Map();
|
|
515
547
|
this.lazyStatus = new Map();
|
|
516
548
|
this.showToggle = true;
|
|
517
549
|
this.showCounts = true;
|
|
@@ -531,23 +563,28 @@ class SlicePanelComponent {
|
|
|
531
563
|
this._countCache = new Map();
|
|
532
564
|
this.trackBySliceIndex = (index, _) => index;
|
|
533
565
|
this.trackByCategoryKey = (_, cat) => cat.key;
|
|
566
|
+
this._columnIndex = new Map();
|
|
534
567
|
}
|
|
535
568
|
get hostCollapsed() { return this.isCollapsed; }
|
|
536
569
|
ngOnInit() {
|
|
537
570
|
this.isCollapsed = this.collapsedByDefault;
|
|
538
571
|
}
|
|
539
572
|
ngOnChanges(changes) {
|
|
540
|
-
if (changes['sliceConfigs'] || changes['data'] || changes['columns'] || changes['
|
|
573
|
+
if (changes['sliceConfigs'] || changes['data'] || changes['columns'] || changes['lazyStatus']) {
|
|
574
|
+
const hadActiveFilters = this._hasActiveFilters();
|
|
541
575
|
this._refreshDynamicSlices();
|
|
542
576
|
this._rebuildCache();
|
|
543
|
-
const shouldReemitFilter = !!(changes['
|
|
544
|
-
|
|
545
|
-
if (
|
|
577
|
+
const shouldReemitFilter = !!(changes['sliceConfigs'] || changes['data'] || changes['columns'] || changes['lazyStatus']);
|
|
578
|
+
// Un slice retiré de la config emporte sa sélection : le parent doit en être informé.
|
|
579
|
+
if (shouldReemitFilter && (hadActiveFilters || this._hasActiveFilters())) {
|
|
546
580
|
this._emitFilter();
|
|
547
581
|
}
|
|
548
582
|
this._cdr.markForCheck();
|
|
549
583
|
}
|
|
550
584
|
}
|
|
585
|
+
_hasActiveFilters() {
|
|
586
|
+
return [...this.activeKeysBySlice.values()].some(s => s.size > 0);
|
|
587
|
+
}
|
|
551
588
|
get showPanel() {
|
|
552
589
|
const hasCfg = (this.sliceConfigs || []).length > 0 || this._dynamicSlices.length > 0;
|
|
553
590
|
if (this.alwaysShow)
|
|
@@ -656,14 +693,16 @@ class SlicePanelComponent {
|
|
|
656
693
|
return !this.activeKeysBySlice.has(sliceIndex) || this.activeKeysBySlice.get(sliceIndex).size === 0;
|
|
657
694
|
}
|
|
658
695
|
sliceCategoryCount(sliceIndex, categoryKey) {
|
|
659
|
-
const cacheKey = `${sliceIndex}:${categoryKey}`;
|
|
660
|
-
if (this._countCache.has(cacheKey)) {
|
|
661
|
-
return this._countCache.get(cacheKey);
|
|
662
|
-
}
|
|
663
696
|
const slice = this._cachedSlices[sliceIndex];
|
|
664
697
|
if (!slice)
|
|
665
698
|
return 0;
|
|
666
699
|
const category = (slice.categories || []).find((c) => c.key === categoryKey);
|
|
700
|
+
if (category?.count !== undefined)
|
|
701
|
+
return category.count;
|
|
702
|
+
const cacheKey = `${sliceIndex}:${categoryKey}`;
|
|
703
|
+
if (this._countCache.has(cacheKey)) {
|
|
704
|
+
return this._countCache.get(cacheKey);
|
|
705
|
+
}
|
|
667
706
|
const count = category ? (this.data || []).filter((row) => category.filter ? category.filter(row) : true).length : 0;
|
|
668
707
|
this._countCache.set(cacheKey, count);
|
|
669
708
|
return count;
|
|
@@ -731,11 +770,11 @@ class SlicePanelComponent {
|
|
|
731
770
|
addDynamicSlice(column) {
|
|
732
771
|
if (this._dynamicSlices.some(slice => slice.key === column.key))
|
|
733
772
|
return;
|
|
734
|
-
const
|
|
773
|
+
const distinct = this._computeDistinctValues(column.key);
|
|
735
774
|
const newSlice = {
|
|
736
775
|
title: column.header,
|
|
737
776
|
columnKey: column.key,
|
|
738
|
-
categories:
|
|
777
|
+
categories: this._buildCategories(column.key, distinct),
|
|
739
778
|
};
|
|
740
779
|
this._dynamicSlices = [...this._dynamicSlices, { key: column.key, slice: newSlice }];
|
|
741
780
|
this._rebuildCache();
|
|
@@ -747,14 +786,7 @@ class SlicePanelComponent {
|
|
|
747
786
|
const dynamicIndex = sliceIndex - this.staticSliceCount;
|
|
748
787
|
if (dynamicIndex < 0)
|
|
749
788
|
return;
|
|
750
|
-
|
|
751
|
-
this.activeKeysBySlice.forEach((keys, idx) => {
|
|
752
|
-
if (idx < sliceIndex)
|
|
753
|
-
newActiveKeys.set(idx, keys);
|
|
754
|
-
else if (idx > sliceIndex)
|
|
755
|
-
newActiveKeys.set(idx - 1, keys);
|
|
756
|
-
});
|
|
757
|
-
this.activeKeysBySlice = newActiveKeys;
|
|
789
|
+
// Les sélections sont rapprochées des slices restants par identité dans _rebuildCache.
|
|
758
790
|
this._dynamicSlices = this._dynamicSlices.filter((_, i) => i !== dynamicIndex);
|
|
759
791
|
this._rebuildCache();
|
|
760
792
|
this.dynamicSliceKeysChange.emit(this._dynamicSlices.map(d => d.key));
|
|
@@ -811,32 +843,37 @@ class SlicePanelComponent {
|
|
|
811
843
|
if (!slice.columnKey || (slice.categories && slice.categories.length > 0)) {
|
|
812
844
|
return slice;
|
|
813
845
|
}
|
|
814
|
-
const col =
|
|
846
|
+
const col = this._column(slice.columnKey);
|
|
815
847
|
const isLazy = !!col?.lazy;
|
|
816
848
|
if (isLazy && this.lazyStatus.get(slice.columnKey) !== 'loaded') {
|
|
817
849
|
return { ...slice, categories: [] };
|
|
818
850
|
}
|
|
819
851
|
if (slice.bucket) {
|
|
820
|
-
const
|
|
852
|
+
const bucket = slice.bucket;
|
|
853
|
+
const counts = new Map();
|
|
854
|
+
(this.data || []).forEach(row => { const v = bucket(row); counts.set(v, (counts.get(v) ?? 0) + 1); });
|
|
821
855
|
return {
|
|
822
856
|
...slice,
|
|
823
|
-
categories:
|
|
824
|
-
key: v, label: v,
|
|
825
|
-
filter: (row) =>
|
|
857
|
+
categories: [...counts.keys()].sort().map((v) => ({
|
|
858
|
+
key: v, label: v, count: counts.get(v),
|
|
859
|
+
filter: (row) => bucket(row) === v,
|
|
826
860
|
})),
|
|
827
861
|
};
|
|
828
862
|
}
|
|
829
|
-
const distinctValues = this._computeDistinctValues(slice.columnKey);
|
|
830
863
|
return {
|
|
831
864
|
...slice,
|
|
832
|
-
categories:
|
|
865
|
+
categories: this._buildCategories(slice.columnKey, this._computeDistinctValues(slice.columnKey)),
|
|
833
866
|
};
|
|
834
867
|
}
|
|
835
|
-
|
|
868
|
+
_buildCategories(columnKey, distinct) {
|
|
869
|
+
return distinct.values.map((v) => this._buildCategoryFromValue(columnKey, v, distinct.counts.get(v)));
|
|
870
|
+
}
|
|
871
|
+
_buildCategoryFromValue(columnKey, v, count) {
|
|
836
872
|
if (v === EMPTY_CATEGORY_KEY) {
|
|
837
873
|
return {
|
|
838
874
|
key: EMPTY_CATEGORY_KEY,
|
|
839
875
|
label: EMPTY_CATEGORY_LABEL,
|
|
876
|
+
count,
|
|
840
877
|
filter: (row) => {
|
|
841
878
|
const val = this._getLazyAwareValue(columnKey, row);
|
|
842
879
|
return val == null || val === '';
|
|
@@ -844,24 +881,44 @@ class SlicePanelComponent {
|
|
|
844
881
|
};
|
|
845
882
|
}
|
|
846
883
|
return {
|
|
847
|
-
key: v, label: v,
|
|
884
|
+
key: v, label: v, count,
|
|
848
885
|
filter: (row) => String(this._getLazyAwareValue(columnKey, row) ?? '') === v,
|
|
849
886
|
};
|
|
850
887
|
}
|
|
888
|
+
/** Valeurs distinctes (triées, « vide » en dernier) et effectifs, calculés en un seul passage. */
|
|
851
889
|
_computeDistinctValues(columnKey) {
|
|
852
|
-
const
|
|
890
|
+
const counts = new Map();
|
|
891
|
+
let empty = 0;
|
|
892
|
+
(this.data || []).forEach((row) => {
|
|
853
893
|
const val = this._getLazyAwareValue(columnKey, row);
|
|
854
|
-
|
|
894
|
+
if (val != null && val !== '') {
|
|
895
|
+
const text = String(val);
|
|
896
|
+
counts.set(text, (counts.get(text) ?? 0) + 1);
|
|
897
|
+
}
|
|
898
|
+
else {
|
|
899
|
+
empty++;
|
|
900
|
+
}
|
|
855
901
|
});
|
|
856
|
-
const
|
|
857
|
-
if (
|
|
858
|
-
|
|
859
|
-
|
|
902
|
+
const values = [...counts.keys()].sort();
|
|
903
|
+
if (empty > 0) {
|
|
904
|
+
values.push(EMPTY_CATEGORY_KEY);
|
|
905
|
+
counts.set(EMPTY_CATEGORY_KEY, empty);
|
|
906
|
+
}
|
|
907
|
+
return { values, counts };
|
|
908
|
+
}
|
|
909
|
+
_column(key) {
|
|
910
|
+
if (this._columnsRef !== this.columns) {
|
|
911
|
+
this._columnsRef = this.columns;
|
|
912
|
+
this._columnIndex = new Map();
|
|
913
|
+
(this.columns || []).forEach(c => { if (!this._columnIndex.has(c.key))
|
|
914
|
+
this._columnIndex.set(c.key, c); });
|
|
915
|
+
}
|
|
916
|
+
return key === undefined ? undefined : this._columnIndex.get(key);
|
|
860
917
|
}
|
|
861
918
|
_getLazyAwareValue(columnKey, row) {
|
|
862
|
-
const colDef =
|
|
863
|
-
if (colDef?.lazy) {
|
|
864
|
-
return
|
|
919
|
+
const colDef = this._column(columnKey);
|
|
920
|
+
if (colDef?.lazy && this.lazyStatus.get(columnKey) !== 'loaded') {
|
|
921
|
+
return null;
|
|
865
922
|
}
|
|
866
923
|
if (colDef?.value) {
|
|
867
924
|
return colDef.value(row, 0);
|
|
@@ -894,16 +951,10 @@ class SlicePanelComponent {
|
|
|
894
951
|
this._countCache.clear();
|
|
895
952
|
}
|
|
896
953
|
_refreshDynamicSlices() {
|
|
897
|
-
this._dynamicSlices = this._dynamicSlices.map(({ key, slice }) => {
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
slice: {
|
|
902
|
-
...slice,
|
|
903
|
-
categories: distinctValues.map((v) => this._buildCategoryFromValue(key, v)),
|
|
904
|
-
},
|
|
905
|
-
};
|
|
906
|
-
});
|
|
954
|
+
this._dynamicSlices = this._dynamicSlices.map(({ key, slice }) => ({
|
|
955
|
+
key,
|
|
956
|
+
slice: { ...slice, categories: this._buildCategories(key, this._computeDistinctValues(key)) },
|
|
957
|
+
}));
|
|
907
958
|
}
|
|
908
959
|
_scrollSliceTitleIntoView(sliceIndex) {
|
|
909
960
|
setTimeout(() => {
|
|
@@ -915,19 +966,17 @@ class SlicePanelComponent {
|
|
|
915
966
|
}, 50);
|
|
916
967
|
}
|
|
917
968
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SlicePanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
918
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: SlicePanelComponent, isStandalone: true, selector: "slice-panel", inputs: { sliceConfigs: "sliceConfigs", columns: "columns", data: "data", lazyData: "lazyData", lazyStatus: "lazyStatus", showToggle: "showToggle", showCounts: "showCounts", collapsedByDefault: "collapsedByDefault", alwaysShow: "alwaysShow" }, outputs: { filterChange: "filterChange", activeKeysChange: "activeKeysChange", dynamicSliceKeysChange: "dynamicSliceKeysChange", collapsedChange: "collapsedChange" }, host: { properties: { "class.collapsed": "this.hostCollapsed" } }, viewQueries: [{ propertyName: "slicePanelBodyRef", first: true, predicate: ["slicePanelBody"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"slice-panel-wrapper\" [class.collapsed]=\"isCollapsed\">\n <aside class=\"slice-panel\" [class.collapsed]=\"isCollapsed\">\n <div class=\"slice-panel-header\">\n <span *ngIf=\"!isCollapsed\" class=\"slice-panel-title\">\n {{ i18n.filterPanelTitle(slices.length) }}\n </span>\n <button\n *ngIf=\"showToggle\"\n type=\"button\"\n class=\"slice-toggle\"\n [attr.aria-label]=\"isCollapsed ? i18n.expandFilterTitle : i18n.collapseFilterTitle\"\n (click)=\"togglePanel()\"\n >\n <mat-icon class=\"slice-toggle-mat-icon\">{{ isCollapsed ? 'filter_list' : 'filter_list_off' }}</mat-icon>\n </button>\n </div>\n\n <div class=\"slice-panel-body\" *ngIf=\"!isCollapsed\" #slicePanelBody>\n <ng-container *ngFor=\"let slice of slices; let si = index; trackBy: trackBySliceIndex\">\n <div class=\"slice-title-row\" [attr.data-slice-index]=\"si\" (click)=\"toggleSliceCollapse(si)\">\n <span class=\"slice-title-chevron\" [class.expanded]=\"!isSliceCollapsed(si)\">\u203A</span>\n <mat-icon *ngIf=\"getSliceIcon(slice)\" class=\"slice-title-icon\">{{ getSliceIcon(slice) }}</mat-icon>\n <p class=\"slice-title\">{{ slice.title || i18n.filterBlockDefaultTitle }}</p>\n <button\n *ngIf=\"isSliceDynamic(si)\"\n type=\"button\"\n class=\"slice-remove-btn\"\n (click)=\"$event.stopPropagation(); removeDynamicSlice(si)\"\n >\u00D7</button>\n </div>\n\n <div class=\"slice-section-items\" *ngIf=\"!isSliceCollapsed(si)\">\n <ng-container *ngIf=\"isSliceLoading(si); else sliceContent\">\n <div class=\"slice-lazy-loading\">\n <span class=\"lazy-skeleton\"></span>\n <span class=\"lazy-skeleton\"></span>\n <span class=\"lazy-skeleton\"></span>\n </div>\n </ng-container>\n <ng-template #sliceContent>\n <ng-container *ngFor=\"let category of slice.categories; trackBy: trackByCategoryKey\">\n <button\n *ngIf=\"sliceCategoryCount(si, category.key) > 0\"\n class=\"slice-item\"\n type=\"button\"\n [class.active]=\"isSliceCategoryActive(si, category.key)\"\n [attr.data-category-key]=\"category.key\"\n [title]=\"category.label\"\n (click)=\"onSliceCategorySelected(si, category.key)\"\n >\n <span>{{ category.label }}</span>\n <span *ngIf=\"showCounts\">{{ sliceCategoryCount(si, category.key) }}</span>\n </button>\n </ng-container>\n <div *ngIf=\"isSliceEmpty(si)\" class=\"slice-empty-state\">{{ i18n.filterEmptyState }}</div>\n </ng-template>\n </div>\n\n <hr *ngIf=\"si < slices.length - 1\" class=\"slice-divider\" />\n </ng-container>\n </div>\n </aside>\n</div>\n", styles: ["@keyframes slideInLeft{0%{opacity:0;transform:translate(-24px)}to{opacity:1;transform:translate(0)}}:host{display:block;height:100%;min-height:0;box-sizing:border-box;animation:slideInLeft .3s cubic-bezier(.34,1.56,.64,1) forwards}.slice-panel-wrapper{height:100%;max-height:100%;overflow-y:auto;box-sizing:border-box}.slice-panel{margin-top:0;border:1px solid var(--jqt-border-color);border-radius:10px;background:var(--jqt-surface-color);display:flex;flex-direction:column;min-width:0;height:100%;overflow:hidden;box-sizing:border-box}.slice-panel-header{flex-shrink:0;display:flex;justify-content:flex-end;align-items:center;height:34px;padding:0 8px;margin-bottom:8px}.slice-panel-header:has(.slice-panel-title){justify-content:space-between;padding-left:12px}.slice-panel-body{flex:1 1 0;min-height:0;overflow-y:auto;display:flex;flex-direction:column;gap:4px;padding:4px 10px 10px;scrollbar-width:thin;scrollbar-color:var(--jqt-border-color) transparent}.slice-panel-body::-webkit-scrollbar{width:2px}.slice-panel-body::-webkit-scrollbar-thumb{background:var(--jqt-primary-color);border-radius:4px}.slice-panel-title{margin:0;padding-top:2px;font-size:11px;font-weight:700;line-height:1;text-transform:uppercase;letter-spacing:.08em;color:var(--jqt-text-color);-webkit-user-select:none;user-select:none;align-self:center;display:inline-flex;align-items:center;gap:5px}.slice-panel-title .slice-panel-title-icon{font-size:16px;width:16px;height:16px;line-height:1;flex-shrink:0}.slice-section-items{display:flex;flex-direction:column;gap:2px;flex:0 0 auto;scrollbar-width:thin;scrollbar-color:var(--jqt-border-color) transparent}.slice-section-items::-webkit-scrollbar{width:2px}.slice-section-items .slice-empty-state{padding:8px 10px;font-size:12px;color:var(--jqt-text-muted, #9ca3af);font-style:italic;text-align:center}.slice-section-items::-webkit-scrollbar-thumb{background:var(--jqt-border-color);border-radius:4px}.slice-toggle{border:0;background:var(--jqt-surface-color);color:var(--jqt-text-color);border-radius:6px;width:22px;height:22px;padding:0;font-size:14px;text-align:center;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;line-height:1}.slice-toggle-mat-icon{font-size:16px;width:16px;height:16px;line-height:1}.slice-panel.collapsed{border-color:transparent;background:transparent;overflow:visible}.slice-panel.collapsed .slice-panel-header{height:34px;padding:0;justify-content:center;align-items:center}.slice-panel.collapsed .slice-toggle{width:34px;height:34px;border:1px solid var(--jqt-border-color);background:var(--jqt-surface-color);border-radius:8px}.slice-panel.collapsed .slice-toggle:hover{border-color:var(--jqt-text-muted)}.slice-panel.collapsed .slice-toggle .slice-toggle-mat-icon{font-size:18px;width:18px;height:18px}.slice-title-row{display:flex;align-items:center;justify-content:space-between;gap:6px;margin:4px 0 6px;cursor:pointer;-webkit-user-select:none;user-select:none}.slice-title-row:hover .slice-title{color:var(--jqt-text-color)}.slice-title-row:hover .slice-title-chevron{color:var(--jqt-text-secondary)}.slice-title-row:first-of-type .slice-title{margin-top:0}.slice-title-chevron{flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;font-size:16px;line-height:1;color:var(--jqt-text-muted);transition:transform .15s ease;transform:rotate(0);position:relative;top:-1px}.slice-title-chevron.expanded{transform:rotate(90deg)}.slice-title{flex:1 1 auto;margin:0;font-size:14px;font-weight:600;color:var(--jqt-title-color)}.slice-title-icon{flex-shrink:0;font-size:14px!important;width:14px!important;height:14px!important;color:var(--jqt-title-color)}.slice-remove-btn{background:none;border:none;cursor:pointer;color:var(--jqt-border-color);font-size:12px;padding:0 2px;line-height:1;flex-shrink:0;opacity:.5;transition:opacity .15s,color .15s}.slice-remove-btn:hover{color:var(--jqt-text-secondary);opacity:1}.slice-title-row:hover .slice-remove-btn{opacity:.8}.slice-item{display:flex;justify-content:space-between;align-items:center;text-align:left;width:100%;padding:5px 10px 5px 16px;background:transparent;border:0;cursor:pointer;border-radius:8px;color:var(--jqt-text-color);box-sizing:border-box;min-width:0;gap:8px}.slice-item.active{background:var(--jqt-row-hover-bg);font-weight:600}.slice-item.slice-multi{padding-left:6px}.slice-item span:first-child{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.slice-item span:last-child{flex:0 0 auto;min-width:1.2em;text-align:right}.slice-check-box{flex:0 0 16px;width:16px;height:16px;border:1.5px solid var(--jqt-text-muted);border-radius:4px;background:var(--jqt-surface-color);display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;transition:background .15s,border-color .15s}.slice-check-box.checked{background:var(--jqt-text-secondary);border-color:var(--jqt-text-secondary)}.slice-check-box.checked:after{content:\"\";display:block;width:9px;height:5px;border-left:2px solid #fff;border-bottom:2px solid #fff;transform:rotate(-45deg) translateY(-1px)}.slice-divider{border:none;border-top:1px solid var(--jqt-border-color);margin:6px 0}.lazy-skeleton{display:block;height:12px;width:70%;border-radius:4px;background:linear-gradient(90deg,var(--jqt-border-color) 25%,var(--jqt-surface-alt-color) 50%,var(--jqt-border-color) 75%);background-size:200% 100%;animation:lazy-shimmer 1.4s ease-in-out infinite}.slice-lazy-loading{display:flex;flex-direction:column;gap:8px;padding:8px 10px}.slice-lazy-loading .lazy-skeleton{width:80%}.slice-lazy-loading .lazy-skeleton:nth-child(2){width:60%}.slice-lazy-loading .lazy-skeleton:nth-child(3){width:45%}@keyframes lazy-shimmer{0%{background-position:200% 0}to{background-position:-200% 0}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
969
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: SlicePanelComponent, isStandalone: true, selector: "slice-panel", inputs: { sliceConfigs: "sliceConfigs", columns: "columns", data: "data", lazyStatus: "lazyStatus", showToggle: "showToggle", showCounts: "showCounts", collapsedByDefault: "collapsedByDefault", alwaysShow: "alwaysShow" }, outputs: { filterChange: "filterChange", activeKeysChange: "activeKeysChange", dynamicSliceKeysChange: "dynamicSliceKeysChange", collapsedChange: "collapsedChange" }, host: { properties: { "class.collapsed": "this.hostCollapsed" } }, viewQueries: [{ propertyName: "slicePanelBodyRef", first: true, predicate: ["slicePanelBody"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"slice-panel-wrapper\" [class.collapsed]=\"isCollapsed\">\n <aside class=\"slice-panel\" [class.collapsed]=\"isCollapsed\">\n <div class=\"slice-panel-header\">\n <span *ngIf=\"!isCollapsed\" class=\"slice-panel-title\">\n {{ i18n.filterPanelTitle(slices.length) }}\n </span>\n <button\n *ngIf=\"showToggle\"\n type=\"button\"\n class=\"slice-toggle\"\n [attr.aria-label]=\"isCollapsed ? i18n.expandFilterTitle : i18n.collapseFilterTitle\"\n (click)=\"togglePanel()\"\n >\n <mat-icon class=\"slice-toggle-mat-icon\">{{ isCollapsed ? 'filter_list' : 'filter_list_off' }}</mat-icon>\n </button>\n </div>\n\n <div class=\"slice-panel-body\" *ngIf=\"!isCollapsed\" #slicePanelBody>\n <ng-container *ngFor=\"let slice of slices; let si = index; trackBy: trackBySliceIndex\">\n <div class=\"slice-title-row\" [attr.data-slice-index]=\"si\">\n <button\n type=\"button\"\n class=\"slice-title-toggle\"\n [attr.aria-controls]=\"'slice-section-' + si\"\n [attr.aria-expanded]=\"!isSliceCollapsed(si)\"\n (click)=\"toggleSliceCollapse(si)\"\n >\n <span class=\"slice-title-chevron\" [class.expanded]=\"!isSliceCollapsed(si)\">\u203A</span>\n <mat-icon *ngIf=\"getSliceIcon(slice)\" class=\"slice-title-icon\">{{ getSliceIcon(slice) }}</mat-icon>\n <span class=\"slice-title\">{{ slice.title || i18n.filterBlockDefaultTitle }}</span>\n </button>\n <button\n *ngIf=\"isSliceDynamic(si)\"\n type=\"button\"\n class=\"slice-remove-btn\"\n [attr.aria-label]=\"'Supprimer le filtre ' + (slice.title || i18n.filterBlockDefaultTitle)\"\n (click)=\"removeDynamicSlice(si)\"\n >\u00D7</button>\n </div>\n\n <div [id]=\"'slice-section-' + si\" class=\"slice-section-items\" *ngIf=\"!isSliceCollapsed(si)\">\n <ng-container *ngIf=\"isSliceLoading(si); else sliceContent\">\n <div class=\"slice-lazy-loading\">\n <span class=\"lazy-skeleton\"></span>\n <span class=\"lazy-skeleton\"></span>\n <span class=\"lazy-skeleton\"></span>\n </div>\n </ng-container>\n <ng-template #sliceContent>\n <ng-container *ngFor=\"let category of slice.categories; trackBy: trackByCategoryKey\">\n <button\n *ngIf=\"sliceCategoryCount(si, category.key) > 0\"\n class=\"slice-item\"\n type=\"button\"\n [class.active]=\"isSliceCategoryActive(si, category.key)\"\n [attr.data-category-key]=\"category.key\"\n [title]=\"category.label\"\n (click)=\"onSliceCategorySelected(si, category.key)\"\n >\n <span>{{ category.label }}</span>\n <span *ngIf=\"showCounts\">{{ sliceCategoryCount(si, category.key) }}</span>\n </button>\n </ng-container>\n <div *ngIf=\"isSliceEmpty(si)\" class=\"slice-empty-state\">{{ i18n.filterEmptyState }}</div>\n </ng-template>\n </div>\n\n <hr *ngIf=\"si < slices.length - 1\" class=\"slice-divider\" />\n </ng-container>\n </div>\n </aside>\n</div>\n", styles: ["@keyframes slideInLeft{0%{opacity:0;transform:translate(-24px)}to{opacity:1;transform:translate(0)}}:host{display:block;height:100%;min-height:0;box-sizing:border-box;animation:slideInLeft .3s cubic-bezier(.34,1.56,.64,1) forwards}.slice-panel-wrapper{height:100%;max-height:100%;overflow-y:auto;box-sizing:border-box}.slice-panel{margin-top:0;border:1px solid var(--jqt-border-color);border-radius:10px;background:var(--jqt-surface-color);display:flex;flex-direction:column;min-width:0;height:100%;overflow:hidden;box-sizing:border-box}.slice-panel-header{flex-shrink:0;display:flex;justify-content:flex-end;align-items:center;height:var(--jqt-slice-header-height, 34px);padding:0 8px;margin-bottom:var(--jqt-slice-header-margin-bottom, 8px)}.slice-panel-header:has(.slice-panel-title){justify-content:space-between;padding-left:12px}.slice-panel-body{flex:1 1 0;min-height:0;overflow-y:auto;display:flex;flex-direction:column;gap:4px;padding:var(--jqt-slice-body-padding, 4px 10px 10px);scrollbar-width:thin;scrollbar-color:var(--jqt-border-color) transparent}.slice-panel-body::-webkit-scrollbar{width:2px}.slice-panel-body::-webkit-scrollbar-thumb{background:var(--jqt-primary-color);border-radius:4px}.slice-panel-title{margin:0;padding-top:2px;font-size:11px;font-weight:700;line-height:1;text-transform:uppercase;letter-spacing:.08em;color:var(--jqt-text-color);-webkit-user-select:none;user-select:none;align-self:center;display:inline-flex;align-items:center;gap:5px}.slice-panel-title .slice-panel-title-icon{font-size:16px;width:16px;height:16px;line-height:1;flex-shrink:0}.slice-section-items{display:flex;flex-direction:column;gap:2px;flex:0 0 auto;scrollbar-width:thin;scrollbar-color:var(--jqt-border-color) transparent}.slice-section-items::-webkit-scrollbar{width:2px}.slice-section-items .slice-empty-state{padding:8px 10px;font-size:12px;color:var(--jqt-text-muted, #9ca3af);font-style:italic;text-align:center}.slice-section-items::-webkit-scrollbar-thumb{background:var(--jqt-border-color);border-radius:4px}.slice-toggle{border:0;background:var(--jqt-surface-color);color:var(--jqt-text-color);border-radius:6px;width:22px;height:22px;padding:0;font-size:14px;text-align:center;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;line-height:1}.slice-toggle-mat-icon{font-size:16px;width:16px;height:16px;line-height:1}.slice-panel.collapsed{border-color:transparent;background:transparent;overflow:visible}.slice-panel.collapsed .slice-panel-header{height:34px;padding:0;justify-content:center;align-items:center}.slice-panel.collapsed .slice-toggle{width:34px;height:34px;border:1px solid var(--jqt-border-color);background:var(--jqt-surface-color);border-radius:8px}.slice-panel.collapsed .slice-toggle:hover{border-color:var(--jqt-text-muted)}.slice-panel.collapsed .slice-toggle .slice-toggle-mat-icon{font-size:18px;width:18px;height:18px}.slice-title-row{display:flex;align-items:center;justify-content:space-between;gap:6px;margin:4px 0 6px;-webkit-user-select:none;user-select:none}.slice-title-row:first-of-type .slice-title{margin-top:0}.slice-title-toggle{flex:1 1 auto;min-width:0;display:flex;align-items:center;gap:6px;padding:0;border:0;background:transparent;color:inherit;cursor:pointer;text-align:left}.slice-title-toggle:hover .slice-title{color:var(--jqt-text-color)}.slice-title-toggle:hover .slice-title-chevron{color:var(--jqt-text-secondary)}.slice-title-toggle:focus-visible{outline:2px solid var(--jqt-primary-color);outline-offset:2px;border-radius:4px}.slice-title-chevron{flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;font-size:16px;line-height:1;color:var(--jqt-text-muted);transition:transform .15s ease;transform:rotate(0);position:relative;top:-1px}.slice-title-chevron.expanded{transform:rotate(90deg)}.slice-title{flex:1 1 auto;margin:0;font-size:var(--jqt-slice-section-title-font-size, 14px);font-weight:600;color:var(--jqt-title-color)}.slice-title-icon{flex-shrink:0;font-size:14px!important;width:14px!important;height:14px!important;color:var(--jqt-title-color)}.slice-remove-btn{background:none;border:none;cursor:pointer;color:var(--jqt-border-color);font-size:12px;padding:0 2px;line-height:1;flex-shrink:0;opacity:.5;transition:opacity .15s,color .15s}.slice-remove-btn:hover{color:var(--jqt-text-secondary);opacity:1}.slice-title-row:hover .slice-remove-btn{opacity:.8}.slice-item{display:flex;justify-content:space-between;align-items:center;text-align:left;width:100%;min-height:var(--jqt-slice-item-min-height, 30px);padding:var(--jqt-slice-item-padding, 5px 10px 5px 16px);background:transparent;border:0;cursor:pointer;border-radius:8px;color:var(--jqt-text-color);box-sizing:border-box;min-width:0;gap:8px;font-size:var(--jqt-slice-item-font-size, 13px)}.slice-item.active{background:var(--jqt-row-hover-bg);font-weight:600}.slice-item.slice-multi{padding-left:6px}.slice-item span:first-child{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.slice-item span:last-child{flex:0 0 auto;min-width:1.2em;text-align:right}.slice-check-box{flex:0 0 16px;width:16px;height:16px;border:1.5px solid var(--jqt-text-muted);border-radius:4px;background:var(--jqt-surface-color);display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;transition:background .15s,border-color .15s}.slice-check-box.checked{background:var(--jqt-text-secondary);border-color:var(--jqt-text-secondary)}.slice-check-box.checked:after{content:\"\";display:block;width:9px;height:5px;border-left:2px solid #fff;border-bottom:2px solid #fff;transform:rotate(-45deg) translateY(-1px)}.slice-divider{border:none;border-top:1px solid var(--jqt-border-color);margin:6px 0}.lazy-skeleton{display:block;height:12px;width:70%;border-radius:4px;background:linear-gradient(90deg,var(--jqt-border-color) 25%,var(--jqt-surface-alt-color) 50%,var(--jqt-border-color) 75%);background-size:200% 100%;animation:lazy-shimmer 1.4s ease-in-out infinite}.slice-lazy-loading{display:flex;flex-direction:column;gap:8px;padding:8px 10px}.slice-lazy-loading .lazy-skeleton{width:80%}.slice-lazy-loading .lazy-skeleton:nth-child(2){width:60%}.slice-lazy-loading .lazy-skeleton:nth-child(3){width:45%}@keyframes lazy-shimmer{0%{background-position:200% 0}to{background-position:-200% 0}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
919
970
|
}
|
|
920
971
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SlicePanelComponent, decorators: [{
|
|
921
972
|
type: Component,
|
|
922
|
-
args: [{ standalone: true, selector: 'slice-panel', imports: [CommonModule, MatIconModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"slice-panel-wrapper\" [class.collapsed]=\"isCollapsed\">\n <aside class=\"slice-panel\" [class.collapsed]=\"isCollapsed\">\n <div class=\"slice-panel-header\">\n <span *ngIf=\"!isCollapsed\" class=\"slice-panel-title\">\n {{ i18n.filterPanelTitle(slices.length) }}\n </span>\n <button\n *ngIf=\"showToggle\"\n type=\"button\"\n class=\"slice-toggle\"\n [attr.aria-label]=\"isCollapsed ? i18n.expandFilterTitle : i18n.collapseFilterTitle\"\n (click)=\"togglePanel()\"\n >\n <mat-icon class=\"slice-toggle-mat-icon\">{{ isCollapsed ? 'filter_list' : 'filter_list_off' }}</mat-icon>\n </button>\n </div>\n\n <div class=\"slice-panel-body\" *ngIf=\"!isCollapsed\" #slicePanelBody>\n <ng-container *ngFor=\"let slice of slices; let si = index; trackBy: trackBySliceIndex\">\n <div class=\"slice-title-row\" [attr.data-slice-index]=\"si\" (click)=\"toggleSliceCollapse(si)\">\n
|
|
973
|
+
args: [{ standalone: true, selector: 'slice-panel', imports: [CommonModule, MatIconModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"slice-panel-wrapper\" [class.collapsed]=\"isCollapsed\">\n <aside class=\"slice-panel\" [class.collapsed]=\"isCollapsed\">\n <div class=\"slice-panel-header\">\n <span *ngIf=\"!isCollapsed\" class=\"slice-panel-title\">\n {{ i18n.filterPanelTitle(slices.length) }}\n </span>\n <button\n *ngIf=\"showToggle\"\n type=\"button\"\n class=\"slice-toggle\"\n [attr.aria-label]=\"isCollapsed ? i18n.expandFilterTitle : i18n.collapseFilterTitle\"\n (click)=\"togglePanel()\"\n >\n <mat-icon class=\"slice-toggle-mat-icon\">{{ isCollapsed ? 'filter_list' : 'filter_list_off' }}</mat-icon>\n </button>\n </div>\n\n <div class=\"slice-panel-body\" *ngIf=\"!isCollapsed\" #slicePanelBody>\n <ng-container *ngFor=\"let slice of slices; let si = index; trackBy: trackBySliceIndex\">\n <div class=\"slice-title-row\" [attr.data-slice-index]=\"si\">\n <button\n type=\"button\"\n class=\"slice-title-toggle\"\n [attr.aria-controls]=\"'slice-section-' + si\"\n [attr.aria-expanded]=\"!isSliceCollapsed(si)\"\n (click)=\"toggleSliceCollapse(si)\"\n >\n <span class=\"slice-title-chevron\" [class.expanded]=\"!isSliceCollapsed(si)\">\u203A</span>\n <mat-icon *ngIf=\"getSliceIcon(slice)\" class=\"slice-title-icon\">{{ getSliceIcon(slice) }}</mat-icon>\n <span class=\"slice-title\">{{ slice.title || i18n.filterBlockDefaultTitle }}</span>\n </button>\n <button\n *ngIf=\"isSliceDynamic(si)\"\n type=\"button\"\n class=\"slice-remove-btn\"\n [attr.aria-label]=\"'Supprimer le filtre ' + (slice.title || i18n.filterBlockDefaultTitle)\"\n (click)=\"removeDynamicSlice(si)\"\n >\u00D7</button>\n </div>\n\n <div [id]=\"'slice-section-' + si\" class=\"slice-section-items\" *ngIf=\"!isSliceCollapsed(si)\">\n <ng-container *ngIf=\"isSliceLoading(si); else sliceContent\">\n <div class=\"slice-lazy-loading\">\n <span class=\"lazy-skeleton\"></span>\n <span class=\"lazy-skeleton\"></span>\n <span class=\"lazy-skeleton\"></span>\n </div>\n </ng-container>\n <ng-template #sliceContent>\n <ng-container *ngFor=\"let category of slice.categories; trackBy: trackByCategoryKey\">\n <button\n *ngIf=\"sliceCategoryCount(si, category.key) > 0\"\n class=\"slice-item\"\n type=\"button\"\n [class.active]=\"isSliceCategoryActive(si, category.key)\"\n [attr.data-category-key]=\"category.key\"\n [title]=\"category.label\"\n (click)=\"onSliceCategorySelected(si, category.key)\"\n >\n <span>{{ category.label }}</span>\n <span *ngIf=\"showCounts\">{{ sliceCategoryCount(si, category.key) }}</span>\n </button>\n </ng-container>\n <div *ngIf=\"isSliceEmpty(si)\" class=\"slice-empty-state\">{{ i18n.filterEmptyState }}</div>\n </ng-template>\n </div>\n\n <hr *ngIf=\"si < slices.length - 1\" class=\"slice-divider\" />\n </ng-container>\n </div>\n </aside>\n</div>\n", styles: ["@keyframes slideInLeft{0%{opacity:0;transform:translate(-24px)}to{opacity:1;transform:translate(0)}}:host{display:block;height:100%;min-height:0;box-sizing:border-box;animation:slideInLeft .3s cubic-bezier(.34,1.56,.64,1) forwards}.slice-panel-wrapper{height:100%;max-height:100%;overflow-y:auto;box-sizing:border-box}.slice-panel{margin-top:0;border:1px solid var(--jqt-border-color);border-radius:10px;background:var(--jqt-surface-color);display:flex;flex-direction:column;min-width:0;height:100%;overflow:hidden;box-sizing:border-box}.slice-panel-header{flex-shrink:0;display:flex;justify-content:flex-end;align-items:center;height:var(--jqt-slice-header-height, 34px);padding:0 8px;margin-bottom:var(--jqt-slice-header-margin-bottom, 8px)}.slice-panel-header:has(.slice-panel-title){justify-content:space-between;padding-left:12px}.slice-panel-body{flex:1 1 0;min-height:0;overflow-y:auto;display:flex;flex-direction:column;gap:4px;padding:var(--jqt-slice-body-padding, 4px 10px 10px);scrollbar-width:thin;scrollbar-color:var(--jqt-border-color) transparent}.slice-panel-body::-webkit-scrollbar{width:2px}.slice-panel-body::-webkit-scrollbar-thumb{background:var(--jqt-primary-color);border-radius:4px}.slice-panel-title{margin:0;padding-top:2px;font-size:11px;font-weight:700;line-height:1;text-transform:uppercase;letter-spacing:.08em;color:var(--jqt-text-color);-webkit-user-select:none;user-select:none;align-self:center;display:inline-flex;align-items:center;gap:5px}.slice-panel-title .slice-panel-title-icon{font-size:16px;width:16px;height:16px;line-height:1;flex-shrink:0}.slice-section-items{display:flex;flex-direction:column;gap:2px;flex:0 0 auto;scrollbar-width:thin;scrollbar-color:var(--jqt-border-color) transparent}.slice-section-items::-webkit-scrollbar{width:2px}.slice-section-items .slice-empty-state{padding:8px 10px;font-size:12px;color:var(--jqt-text-muted, #9ca3af);font-style:italic;text-align:center}.slice-section-items::-webkit-scrollbar-thumb{background:var(--jqt-border-color);border-radius:4px}.slice-toggle{border:0;background:var(--jqt-surface-color);color:var(--jqt-text-color);border-radius:6px;width:22px;height:22px;padding:0;font-size:14px;text-align:center;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;line-height:1}.slice-toggle-mat-icon{font-size:16px;width:16px;height:16px;line-height:1}.slice-panel.collapsed{border-color:transparent;background:transparent;overflow:visible}.slice-panel.collapsed .slice-panel-header{height:34px;padding:0;justify-content:center;align-items:center}.slice-panel.collapsed .slice-toggle{width:34px;height:34px;border:1px solid var(--jqt-border-color);background:var(--jqt-surface-color);border-radius:8px}.slice-panel.collapsed .slice-toggle:hover{border-color:var(--jqt-text-muted)}.slice-panel.collapsed .slice-toggle .slice-toggle-mat-icon{font-size:18px;width:18px;height:18px}.slice-title-row{display:flex;align-items:center;justify-content:space-between;gap:6px;margin:4px 0 6px;-webkit-user-select:none;user-select:none}.slice-title-row:first-of-type .slice-title{margin-top:0}.slice-title-toggle{flex:1 1 auto;min-width:0;display:flex;align-items:center;gap:6px;padding:0;border:0;background:transparent;color:inherit;cursor:pointer;text-align:left}.slice-title-toggle:hover .slice-title{color:var(--jqt-text-color)}.slice-title-toggle:hover .slice-title-chevron{color:var(--jqt-text-secondary)}.slice-title-toggle:focus-visible{outline:2px solid var(--jqt-primary-color);outline-offset:2px;border-radius:4px}.slice-title-chevron{flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;font-size:16px;line-height:1;color:var(--jqt-text-muted);transition:transform .15s ease;transform:rotate(0);position:relative;top:-1px}.slice-title-chevron.expanded{transform:rotate(90deg)}.slice-title{flex:1 1 auto;margin:0;font-size:var(--jqt-slice-section-title-font-size, 14px);font-weight:600;color:var(--jqt-title-color)}.slice-title-icon{flex-shrink:0;font-size:14px!important;width:14px!important;height:14px!important;color:var(--jqt-title-color)}.slice-remove-btn{background:none;border:none;cursor:pointer;color:var(--jqt-border-color);font-size:12px;padding:0 2px;line-height:1;flex-shrink:0;opacity:.5;transition:opacity .15s,color .15s}.slice-remove-btn:hover{color:var(--jqt-text-secondary);opacity:1}.slice-title-row:hover .slice-remove-btn{opacity:.8}.slice-item{display:flex;justify-content:space-between;align-items:center;text-align:left;width:100%;min-height:var(--jqt-slice-item-min-height, 30px);padding:var(--jqt-slice-item-padding, 5px 10px 5px 16px);background:transparent;border:0;cursor:pointer;border-radius:8px;color:var(--jqt-text-color);box-sizing:border-box;min-width:0;gap:8px;font-size:var(--jqt-slice-item-font-size, 13px)}.slice-item.active{background:var(--jqt-row-hover-bg);font-weight:600}.slice-item.slice-multi{padding-left:6px}.slice-item span:first-child{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.slice-item span:last-child{flex:0 0 auto;min-width:1.2em;text-align:right}.slice-check-box{flex:0 0 16px;width:16px;height:16px;border:1.5px solid var(--jqt-text-muted);border-radius:4px;background:var(--jqt-surface-color);display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;transition:background .15s,border-color .15s}.slice-check-box.checked{background:var(--jqt-text-secondary);border-color:var(--jqt-text-secondary)}.slice-check-box.checked:after{content:\"\";display:block;width:9px;height:5px;border-left:2px solid #fff;border-bottom:2px solid #fff;transform:rotate(-45deg) translateY(-1px)}.slice-divider{border:none;border-top:1px solid var(--jqt-border-color);margin:6px 0}.lazy-skeleton{display:block;height:12px;width:70%;border-radius:4px;background:linear-gradient(90deg,var(--jqt-border-color) 25%,var(--jqt-surface-alt-color) 50%,var(--jqt-border-color) 75%);background-size:200% 100%;animation:lazy-shimmer 1.4s ease-in-out infinite}.slice-lazy-loading{display:flex;flex-direction:column;gap:8px;padding:8px 10px}.slice-lazy-loading .lazy-skeleton{width:80%}.slice-lazy-loading .lazy-skeleton:nth-child(2){width:60%}.slice-lazy-loading .lazy-skeleton:nth-child(3){width:45%}@keyframes lazy-shimmer{0%{background-position:200% 0}to{background-position:-200% 0}}\n"] }]
|
|
923
974
|
}], propDecorators: { sliceConfigs: [{
|
|
924
975
|
type: Input
|
|
925
976
|
}], columns: [{
|
|
926
977
|
type: Input
|
|
927
978
|
}], data: [{
|
|
928
979
|
type: Input
|
|
929
|
-
}], lazyData: [{
|
|
930
|
-
type: Input
|
|
931
980
|
}], lazyStatus: [{
|
|
932
981
|
type: Input
|
|
933
982
|
}], showToggle: [{
|
|
@@ -1006,6 +1055,7 @@ var OrganizerButtonEventType;
|
|
|
1006
1055
|
OrganizerButtonEventType["GROUP_BY_SELECTED"] = "groupBySelected";
|
|
1007
1056
|
OrganizerButtonEventType["TEMPLATE_SELECTED"] = "templateSelected";
|
|
1008
1057
|
OrganizerButtonEventType["SLICE_SELECTED"] = "sliceSelected";
|
|
1058
|
+
OrganizerButtonEventType["CHART_TYPE_SELECTED"] = "chartTypeSelected";
|
|
1009
1059
|
OrganizerButtonEventType["VIEW_SWITCHED"] = "viewSwitched";
|
|
1010
1060
|
})(OrganizerButtonEventType || (OrganizerButtonEventType = {}));
|
|
1011
1061
|
function getGroupItems(config) {
|
|
@@ -1023,14 +1073,8 @@ function getStackItems(indicator) {
|
|
|
1023
1073
|
|
|
1024
1074
|
function chartConfigToOrganizer(chartConfig, options = {}) {
|
|
1025
1075
|
if (!chartConfig) {
|
|
1026
|
-
console.debug('[OrganizerChartAdapter] Empty chart config, returning empty organizer config');
|
|
1027
1076
|
return {};
|
|
1028
1077
|
}
|
|
1029
|
-
console.debug('[OrganizerChartAdapter] Converting chart config to organizer', {
|
|
1030
|
-
hasGroups: !!chartConfig.groups,
|
|
1031
|
-
indicatorCount: chartConfig.indicators?.items?.length ?? 0,
|
|
1032
|
-
filterCount: chartConfig.filters?.items?.length ?? 0
|
|
1033
|
-
});
|
|
1034
1078
|
const { onFetchSliceData, onExportVisual, onExportData, switchView } = options;
|
|
1035
1079
|
const indicators = getIndicatorItems(chartConfig);
|
|
1036
1080
|
const activeIndicator = indicators.find(i => i.selected);
|
|
@@ -1064,7 +1108,6 @@ function chartConfigToOrganizer(chartConfig, options = {}) {
|
|
|
1064
1108
|
}
|
|
1065
1109
|
function chartConfigToState(chartConfig) {
|
|
1066
1110
|
if (!chartConfig) {
|
|
1067
|
-
console.debug('[OrganizerChartAdapter] Empty chart config for state conversion');
|
|
1068
1111
|
return {};
|
|
1069
1112
|
}
|
|
1070
1113
|
const indicators = getIndicatorItems(chartConfig);
|
|
@@ -1073,12 +1116,6 @@ function chartConfigToState(chartConfig) {
|
|
|
1073
1116
|
const activeGroup = getGroupItems(chartConfig).find(g => g.selected);
|
|
1074
1117
|
const activeSlices = getFilterItems(chartConfig).filter(f => f.selected);
|
|
1075
1118
|
const indicatorGroup = activeIndicator?.group;
|
|
1076
|
-
console.debug('[OrganizerChartAdapter] Current chart state', {
|
|
1077
|
-
activeGroup: activeGroup?.key,
|
|
1078
|
-
activeIndicator: activeIndicator?.key,
|
|
1079
|
-
activeStack: activeStack?.key,
|
|
1080
|
-
sliceCount: activeSlices.length
|
|
1081
|
-
});
|
|
1082
1119
|
const state = {
|
|
1083
1120
|
selectedX: activeGroup?.key,
|
|
1084
1121
|
selectedY: indicatorGroup ?? activeIndicator?.key,
|
|
@@ -1108,10 +1145,8 @@ function chartConfigToUnifiedState(chartConfig, viewMode = 'chart') {
|
|
|
1108
1145
|
}
|
|
1109
1146
|
function applyOrganizerEventToChart(event, chartConfig) {
|
|
1110
1147
|
if (!chartConfig) {
|
|
1111
|
-
console.debug('[OrganizerChartAdapter] Cannot apply event to empty chart config');
|
|
1112
1148
|
return;
|
|
1113
1149
|
}
|
|
1114
|
-
console.debug('[OrganizerChartAdapter] Applying organizer event', { eventType: event.type, state: event.state });
|
|
1115
1150
|
const state = event.state;
|
|
1116
1151
|
_cleanSynthetics(chartConfig);
|
|
1117
1152
|
if (state.selectedX !== undefined)
|
|
@@ -1125,7 +1160,6 @@ function applyOrganizerEventToChart(event, chartConfig) {
|
|
|
1125
1160
|
event.resolvedYUnit = resolveYUnit(chartConfig, event.state.selectedY, event.state.selectedYAggregate);
|
|
1126
1161
|
}
|
|
1127
1162
|
function handleOrganizerChartEvent(event, chartConfig, currentBinding, options = {}) {
|
|
1128
|
-
console.debug('[OrganizerChartAdapter] Handling organizer event', { eventType: event.type });
|
|
1129
1163
|
if (event.type === 'viewSwitched') {
|
|
1130
1164
|
return {
|
|
1131
1165
|
binding: {
|
|
@@ -1420,19 +1454,10 @@ class OrganizerFacade {
|
|
|
1420
1454
|
}
|
|
1421
1455
|
onDynamicSliceKeysChange(keys) {
|
|
1422
1456
|
const allFields = this._panelConfig.fields ?? [];
|
|
1423
|
-
const staticKeys = new Set((this._panelConfig.sliceConfigs ?? []).map(s => s.columnKey).filter((k) => !!k));
|
|
1424
|
-
const dynamicKeySet = new Set(keys);
|
|
1425
|
-
const existingKeys = new Set([...staticKeys, ...dynamicKeySet]);
|
|
1426
1457
|
this.sliceBy.activeDynamicFields = keys
|
|
1427
1458
|
.map(k => allFields.find(c => c.key === k))
|
|
1428
1459
|
.filter((c) => !!c);
|
|
1429
|
-
|
|
1430
|
-
this.sliceBy.availableForDynamic = allFields.filter(c => {
|
|
1431
|
-
if (c.sliceable === false || existingKeys.has(c.key) || seen.has(c.key))
|
|
1432
|
-
return false;
|
|
1433
|
-
seen.add(c.key);
|
|
1434
|
-
return true;
|
|
1435
|
-
});
|
|
1460
|
+
this.sliceBy.availableForDynamic = this._availableForDynamic(new Set(keys));
|
|
1436
1461
|
this._computeSliceLabel();
|
|
1437
1462
|
this._events$.next({ type: 'dynamicSlicesChanged', keys });
|
|
1438
1463
|
}
|
|
@@ -1457,23 +1482,42 @@ class OrganizerFacade {
|
|
|
1457
1482
|
this.fields.activeFields = [...defaultFields];
|
|
1458
1483
|
}
|
|
1459
1484
|
else {
|
|
1460
|
-
const
|
|
1461
|
-
this.fields.activeFields = this.fields.activeFields
|
|
1485
|
+
const current = this._definitionsByKey();
|
|
1486
|
+
this.fields.activeFields = this.fields.activeFields
|
|
1487
|
+
.filter(c => current.has(c.key))
|
|
1488
|
+
.map(c => current.get(c.key));
|
|
1462
1489
|
}
|
|
1463
1490
|
}
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
const
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1491
|
+
/** Première définition de chaque clé dans la config courante. */
|
|
1492
|
+
_definitionsByKey() {
|
|
1493
|
+
const byKey = new Map();
|
|
1494
|
+
(this._panelConfig.fields || []).forEach(c => { if (!byKey.has(c.key))
|
|
1495
|
+
byKey.set(c.key, c); });
|
|
1496
|
+
return byKey;
|
|
1497
|
+
}
|
|
1498
|
+
_staticSliceKeys() {
|
|
1499
|
+
return new Set((this._panelConfig.sliceConfigs ?? []).map(s => s.columnKey).filter((k) => !!k));
|
|
1500
|
+
}
|
|
1501
|
+
/** Champs proposables en slice dynamique, hors slices statiques et slices déjà actifs. */
|
|
1502
|
+
_availableForDynamic(activeKeys) {
|
|
1503
|
+
const staticKeys = this._staticSliceKeys();
|
|
1504
|
+
const seen = new Set();
|
|
1505
|
+
return (this._panelConfig.fields ?? []).filter(c => {
|
|
1506
|
+
if (c.sliceable === false || staticKeys.has(c.key) || activeKeys.has(c.key) || seen.has(c.key))
|
|
1471
1507
|
return false;
|
|
1472
1508
|
if (!c.header && c.sliceable !== true)
|
|
1473
1509
|
return false;
|
|
1474
|
-
|
|
1510
|
+
seen.add(c.key);
|
|
1475
1511
|
return true;
|
|
1476
1512
|
});
|
|
1513
|
+
}
|
|
1514
|
+
_refreshDynamicSliceMeta() {
|
|
1515
|
+
const allFields = this._panelConfig.fields ?? [];
|
|
1516
|
+
const staticKeys = this._staticSliceKeys();
|
|
1517
|
+
const current = this._definitionsByKey();
|
|
1518
|
+
this.sliceBy.activeDynamicFields = this.sliceBy.activeDynamicFields.map(c => current.get(c.key) ?? c);
|
|
1519
|
+
const activeDynKeys = new Set(this.sliceBy.activeDynamicFields.map(c => c.key));
|
|
1520
|
+
this.sliceBy.availableForDynamic = this._availableForDynamic(activeDynKeys);
|
|
1477
1521
|
const seen2 = new Set();
|
|
1478
1522
|
this.sliceBy.allDynamicFields = allFields.filter(c => {
|
|
1479
1523
|
if (c.sliceable === false || staticKeys.has(c.key) || seen2.has(c.key))
|
|
@@ -1563,6 +1607,8 @@ class GroupByManager {
|
|
|
1563
1607
|
}
|
|
1564
1608
|
}
|
|
1565
1609
|
groupPageForward(groupKey, totalCount) {
|
|
1610
|
+
if (this.groupPageSize <= 0)
|
|
1611
|
+
return;
|
|
1566
1612
|
const maxPage = Math.ceil(totalCount / this.groupPageSize) - 1;
|
|
1567
1613
|
const current = this._groupPages.get(groupKey) ?? 0;
|
|
1568
1614
|
if (current < maxPage) {
|
|
@@ -1587,99 +1633,9 @@ class GroupByManager {
|
|
|
1587
1633
|
}
|
|
1588
1634
|
}
|
|
1589
1635
|
|
|
1590
|
-
const LAZY_FIELD_LOADING_VALUE = '__lazy_loading__';
|
|
1591
|
-
const LAZY_FIELD_ERROR_VALUE = '__lazy_error__';
|
|
1592
|
-
class LazyFieldManager {
|
|
1593
|
-
constructor() {
|
|
1594
|
-
this._status = new Map();
|
|
1595
|
-
this._data = new Map();
|
|
1596
|
-
this._cancels = new Map();
|
|
1597
|
-
}
|
|
1598
|
-
get status() {
|
|
1599
|
-
return this._status;
|
|
1600
|
-
}
|
|
1601
|
-
get data() {
|
|
1602
|
-
return this._data;
|
|
1603
|
-
}
|
|
1604
|
-
getStatus(key) {
|
|
1605
|
-
return this._status.get(key) ?? 'idle';
|
|
1606
|
-
}
|
|
1607
|
-
getRenderType(rowValue) {
|
|
1608
|
-
if (rowValue === LAZY_FIELD_LOADING_VALUE)
|
|
1609
|
-
return 'loading';
|
|
1610
|
-
if (rowValue === LAZY_FIELD_ERROR_VALUE)
|
|
1611
|
-
return 'error';
|
|
1612
|
-
return 'value';
|
|
1613
|
-
}
|
|
1614
|
-
cancelAll() {
|
|
1615
|
-
this._cancels.forEach(s => { s.next(); s.complete(); });
|
|
1616
|
-
this._cancels.clear();
|
|
1617
|
-
this._status = new Map();
|
|
1618
|
-
this._data = new Map();
|
|
1619
|
-
}
|
|
1620
|
-
retry(field, resolvedData, callbacks) {
|
|
1621
|
-
const k = field.key;
|
|
1622
|
-
const prev = this._cancels.get(k);
|
|
1623
|
-
if (prev) {
|
|
1624
|
-
prev.next();
|
|
1625
|
-
prev.complete();
|
|
1626
|
-
}
|
|
1627
|
-
this._cancels.delete(k);
|
|
1628
|
-
const newStatus = new Map(this._status);
|
|
1629
|
-
newStatus.delete(k);
|
|
1630
|
-
this._status = newStatus;
|
|
1631
|
-
const newData = new Map(this._data);
|
|
1632
|
-
newData.delete(k);
|
|
1633
|
-
this._data = newData;
|
|
1634
|
-
this.fetch(field, resolvedData, callbacks);
|
|
1635
|
-
}
|
|
1636
|
-
fetch(field, resolvedData, callbacks) {
|
|
1637
|
-
if (!field.lazy)
|
|
1638
|
-
return;
|
|
1639
|
-
const k = field.key;
|
|
1640
|
-
if (this._status.get(k) === 'loading')
|
|
1641
|
-
return;
|
|
1642
|
-
const prev = this._cancels.get(k);
|
|
1643
|
-
if (prev) {
|
|
1644
|
-
prev.next();
|
|
1645
|
-
prev.complete();
|
|
1646
|
-
}
|
|
1647
|
-
const cancel$ = new Subject();
|
|
1648
|
-
this._cancels.set(k, cancel$);
|
|
1649
|
-
const newStatus = new Map(this._status);
|
|
1650
|
-
newStatus.set(k, 'loading');
|
|
1651
|
-
this._status = newStatus;
|
|
1652
|
-
callbacks.onRefresh();
|
|
1653
|
-
field.lazy.fetchFn().pipe(take(1), takeUntil$1(cancel$)).subscribe({
|
|
1654
|
-
next: (values) => {
|
|
1655
|
-
const rowMap = new Map();
|
|
1656
|
-
values.forEach((value, i) => {
|
|
1657
|
-
if (i < resolvedData.length)
|
|
1658
|
-
rowMap.set(resolvedData[i], value);
|
|
1659
|
-
});
|
|
1660
|
-
const newData = new Map(this._data);
|
|
1661
|
-
newData.set(k, rowMap);
|
|
1662
|
-
this._data = newData;
|
|
1663
|
-
const newStatus2 = new Map(this._status);
|
|
1664
|
-
newStatus2.set(k, 'loaded');
|
|
1665
|
-
this._status = newStatus2;
|
|
1666
|
-
callbacks.onRefresh();
|
|
1667
|
-
callbacks.onMarkForCheck();
|
|
1668
|
-
},
|
|
1669
|
-
error: () => {
|
|
1670
|
-
const newStatusErr = new Map(this._status);
|
|
1671
|
-
newStatusErr.set(k, 'error');
|
|
1672
|
-
this._status = newStatusErr;
|
|
1673
|
-
callbacks.onRefresh();
|
|
1674
|
-
callbacks.onMarkForCheck();
|
|
1675
|
-
},
|
|
1676
|
-
});
|
|
1677
|
-
}
|
|
1678
|
-
}
|
|
1679
|
-
|
|
1680
1636
|
/**
|
|
1681
1637
|
* Generated bundle index. Do not edit.
|
|
1682
1638
|
*/
|
|
1683
1639
|
|
|
1684
|
-
export { DEFAULT_MENU_ICONS, DEFAULT_MENU_LABELS, GroupByManager,
|
|
1640
|
+
export { DEFAULT_MENU_ICONS, DEFAULT_MENU_LABELS, GroupByManager, OrganizerButtonComponent, OrganizerButtonEventType, OrganizerButtonModule, OrganizerFacade, SlicePanelComponent, applyOrganizerEventToChart, buildOrganizerChartBinding, buildYFields, chartConfigToOrganizer, chartConfigToState, chartConfigToUnifiedState, handleOrganizerChartEvent, humanizeKey, resolveYKey, resolveYUnit };
|
|
1685
1641
|
//# sourceMappingURL=oneteme-jquery-organizer.mjs.map
|