@oneteme/jquery-organizer 0.0.1

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.
Files changed (34) hide show
  1. package/README.md +441 -0
  2. package/esm2022/lib/group-by-manager/group-by-manager.mjs +73 -0
  3. package/esm2022/lib/lazy-field-manager/lazy-field-manager.mjs +91 -0
  4. package/esm2022/lib/models/index.mjs +3 -0
  5. package/esm2022/lib/models/organizer-chart-adapter.mjs +164 -0
  6. package/esm2022/lib/models/organizer-config.interface.mjs +2 -0
  7. package/esm2022/lib/models/organizer-menu.model.mjs +17 -0
  8. package/esm2022/lib/models/organizer-utils.mjs +26 -0
  9. package/esm2022/lib/organizer-button/organizer-button.component.mjs +266 -0
  10. package/esm2022/lib/organizer-button/organizer-button.module.mjs +17 -0
  11. package/esm2022/lib/organizer-facade/organizer-facade.mjs +269 -0
  12. package/esm2022/lib/organizer-facade/organizer-facade.types.mjs +2 -0
  13. package/esm2022/lib/slice-panel/slice-panel.component.mjs +452 -0
  14. package/esm2022/lib/slice-panel/slice-panel.model.mjs +2 -0
  15. package/esm2022/oneteme-jquery-organizer.mjs +5 -0
  16. package/esm2022/public-api.mjs +12 -0
  17. package/fesm2022/oneteme-jquery-organizer.mjs +1369 -0
  18. package/fesm2022/oneteme-jquery-organizer.mjs.map +1 -0
  19. package/index.d.ts +5 -0
  20. package/lib/group-by-manager/group-by-manager.d.ts +20 -0
  21. package/lib/lazy-field-manager/lazy-field-manager.d.ts +25 -0
  22. package/lib/models/index.d.ts +2 -0
  23. package/lib/models/organizer-chart-adapter.d.ts +44 -0
  24. package/lib/models/organizer-config.interface.d.ts +103 -0
  25. package/lib/models/organizer-menu.model.d.ts +7 -0
  26. package/lib/models/organizer-utils.d.ts +8 -0
  27. package/lib/organizer-button/organizer-button.component.d.ts +51 -0
  28. package/lib/organizer-button/organizer-button.module.d.ts +8 -0
  29. package/lib/organizer-facade/organizer-facade.d.ts +51 -0
  30. package/lib/organizer-facade/organizer-facade.types.d.ts +31 -0
  31. package/lib/slice-panel/slice-panel.component.d.ts +80 -0
  32. package/lib/slice-panel/slice-panel.model.d.ts +25 -0
  33. package/package.json +35 -0
  34. package/public-api.d.ts +12 -0
@@ -0,0 +1,1369 @@
1
+ import * as i0 from '@angular/core';
2
+ import { EventEmitter, Component, ViewEncapsulation, ChangeDetectionStrategy, Input, Output, ViewChild, inject, ChangeDetectorRef, HostBinding, NgModule } from '@angular/core';
3
+ import * as i1 from '@angular/common';
4
+ import { CommonModule } from '@angular/common';
5
+ import { MatButtonModule } from '@angular/material/button';
6
+ import * as i2 from '@angular/material/icon';
7
+ import { MatIconModule } from '@angular/material/icon';
8
+ import * as i3 from '@angular/material/menu';
9
+ import { MatMenuModule } from '@angular/material/menu';
10
+ import * as i4 from '@angular/material/divider';
11
+ import { MatDividerModule } from '@angular/material/divider';
12
+ import { Subject, isObservable, take, takeUntil as takeUntil$1 } from 'rxjs';
13
+ import { takeUntil } from 'rxjs/operators';
14
+
15
+ class OrganizerButtonComponent {
16
+ constructor(cdr) {
17
+ this.cdr = cdr;
18
+ this.hideMenuValues = false;
19
+ this.viewChange = new EventEmitter();
20
+ this.sliceStateChange = new EventEmitter();
21
+ this.fieldDataCache = new Map();
22
+ this.destroy$ = new Subject();
23
+ }
24
+ ngOnInit() { }
25
+ ngOnDestroy() {
26
+ this.destroy$.next();
27
+ this.destroy$.complete();
28
+ }
29
+ isFieldVisible(fieldId) {
30
+ const visible = this.state?.visibleFields;
31
+ if (!visible) {
32
+ return this.config.fields?.find(f => f.id === fieldId)?.visible !== false;
33
+ }
34
+ return visible.includes(fieldId);
35
+ }
36
+ fieldsSummary() {
37
+ const total = this.config.fields?.length ?? 0;
38
+ const visible = this.state?.visibleFields?.length ?? total;
39
+ return `${visible}/${total}`;
40
+ }
41
+ onFieldToggle(fieldId, visible) {
42
+ const visibleFields = this.state?.visibleFields || [];
43
+ const updated = visible
44
+ ? [...visibleFields, fieldId]
45
+ : visibleFields.filter(f => f !== fieldId);
46
+ this.emitChange('fieldToggled', { visibleFields: updated });
47
+ }
48
+ onXFieldSelect(fieldId) {
49
+ this.emitChange('xSelected', { selectedX: fieldId });
50
+ }
51
+ onYFieldSelect(fieldId) {
52
+ this.emitChange('ySelected', {
53
+ selectedY: fieldId,
54
+ selectedYAggregate: undefined
55
+ });
56
+ }
57
+ onYAggregateSelect(yFieldId, aggregateId) {
58
+ this.emitChange('ySelected', {
59
+ selectedY: yFieldId,
60
+ selectedYAggregate: aggregateId
61
+ });
62
+ }
63
+ onGroupBySelect(groupId) {
64
+ const current = this.state.selectedGroupBy;
65
+ this.emitChange('groupBySelected', { selectedGroupBy: current === groupId ? undefined : groupId });
66
+ }
67
+ onTemplateSelect(templateId) {
68
+ const t = this.config.templates?.find(tmpl => tmpl.id === templateId);
69
+ if (!t)
70
+ return;
71
+ this.emitChange('templateSelected', {
72
+ selectedTemplate: templateId,
73
+ selectedX: t.xField,
74
+ selectedY: t.yField,
75
+ selectedYAggregate: t.yAggregate,
76
+ selectedGroupBy: t.groupBy
77
+ });
78
+ }
79
+ onSliceClick(sliceId) {
80
+ if (this.config.onSliceClick) {
81
+ this.config.onSliceClick();
82
+ }
83
+ const selectedSlices = this.state?.selectedSlices || [];
84
+ let updated;
85
+ if (sliceId) {
86
+ updated = selectedSlices.includes(sliceId) ? [] : [sliceId];
87
+ }
88
+ else {
89
+ updated = selectedSlices;
90
+ }
91
+ if (updated.length === 0 || !sliceId || !this.config.onFetchSliceData) {
92
+ if (this.config.onFetchSliceData) {
93
+ this.sliceStateChange.emit(null);
94
+ }
95
+ this.emitChange('sliceSelected', { selectedSlices: updated });
96
+ return;
97
+ }
98
+ const slice = this.config.slices?.find(s => s.id === sliceId);
99
+ const result = this.config.onFetchSliceData(sliceId);
100
+ const handleData = (tasks) => {
101
+ this.sliceStateChange.emit({
102
+ sliceConfigs: [{ title: slice?.label ?? sliceId, columnKey: sliceId }],
103
+ tasks
104
+ });
105
+ this.emitChange('sliceSelected', { selectedSlices: updated });
106
+ };
107
+ if (isObservable(result)) {
108
+ result.pipe(takeUntil(this.destroy$)).subscribe({ next: handleData });
109
+ }
110
+ else {
111
+ result.then(handleData);
112
+ }
113
+ }
114
+ async loadFieldData(fieldId) {
115
+ if (this.fieldDataCache.has(fieldId))
116
+ return;
117
+ if (!this.config.onFetchFieldData)
118
+ return;
119
+ const field = this.config.fields?.find(f => f.id === fieldId);
120
+ if (!field)
121
+ return;
122
+ this.loadingFieldId = fieldId;
123
+ this.cdr.markForCheck();
124
+ try {
125
+ const data = await this.config.onFetchFieldData(fieldId);
126
+ this.fieldDataCache.set(fieldId, data);
127
+ }
128
+ finally {
129
+ this.loadingFieldId = undefined;
130
+ this.cdr.markForCheck();
131
+ }
132
+ }
133
+ onExport() {
134
+ if (this.config.onExport)
135
+ this.config.onExport();
136
+ this.mainMenuTrigger?.closeMenu();
137
+ }
138
+ onExportVisual() {
139
+ if (this.config.onExportVisual)
140
+ this.config.onExportVisual();
141
+ this.mainMenuTrigger?.closeMenu();
142
+ }
143
+ onExportData() {
144
+ if (this.config.onExportData)
145
+ this.config.onExportData();
146
+ this.mainMenuTrigger?.closeMenu();
147
+ }
148
+ hasExportSubMenu() {
149
+ return !!(this.config.onExportVisual || this.config.onExportData);
150
+ }
151
+ hasSwitchView() {
152
+ return !!this.config.switchView;
153
+ }
154
+ onSwitchView() {
155
+ const sv = this.config.switchView;
156
+ if (!sv)
157
+ return;
158
+ const newView = sv.currentView === 'chart' ? 'table' : 'chart';
159
+ sv.onSwitch(newView);
160
+ this.emitChange('viewSwitched', { viewMode: newView });
161
+ this.mainMenuTrigger?.closeMenu();
162
+ }
163
+ onPreferencesEdit() {
164
+ if (this.config.onPreferencesEdit)
165
+ this.config.onPreferencesEdit();
166
+ this.mainMenuTrigger?.closeMenu();
167
+ }
168
+ onPreferencesSave() {
169
+ if (this.config.onPreferencesSave)
170
+ this.config.onPreferencesSave();
171
+ this.mainMenuTrigger?.closeMenu();
172
+ }
173
+ onPreferencesClear() {
174
+ if (this.config.onPreferencesClear)
175
+ this.config.onPreferencesClear();
176
+ this.mainMenuTrigger?.closeMenu();
177
+ }
178
+ onReset() {
179
+ this.emitChange('reset', {
180
+ visibleFields: [],
181
+ selectedX: undefined,
182
+ selectedY: undefined,
183
+ selectedYAggregate: undefined,
184
+ selectedGroupBy: undefined,
185
+ selectedSlices: []
186
+ });
187
+ }
188
+ activeXLabel() {
189
+ const id = this.state?.selectedX;
190
+ if (!id)
191
+ return '';
192
+ return this.config.xFields?.find(f => f.id === id)?.label ?? id;
193
+ }
194
+ activeYLabel() {
195
+ const yId = this.state?.selectedY;
196
+ if (!yId)
197
+ return '';
198
+ const yField = this.config.yFields?.find(f => f.id === yId);
199
+ const base = yField?.label ?? yId;
200
+ if (this.state?.selectedYAggregate) {
201
+ const agg = yField?.aggregates?.find(a => a.id === this.state.selectedYAggregate);
202
+ return agg ? `${base} · ${agg.label}` : base;
203
+ }
204
+ return base;
205
+ }
206
+ activeGroupByLabel() {
207
+ const id = this.state?.selectedGroupBy;
208
+ if (!id)
209
+ return '';
210
+ return this.config.groups?.find(g => g.id === id)?.label ?? id;
211
+ }
212
+ activeSlicesLabel() {
213
+ const ids = this.state?.selectedSlices;
214
+ if (!ids?.length)
215
+ return '';
216
+ if (ids.length === 1) {
217
+ return this.config.slices?.find(s => s.id === ids[0])?.label ?? ids[0];
218
+ }
219
+ return `${ids.length} sélectionnés`;
220
+ }
221
+ activeTemplateLabel() {
222
+ const id = this.state?.selectedTemplate;
223
+ if (!id)
224
+ return '';
225
+ return this.config.templates?.find(t => t.id === id)?.label ?? id;
226
+ }
227
+ hasChartFields() {
228
+ return (this.config.xFields?.length ?? 0) > 0 || (this.config.yFields?.length ?? 0) > 0;
229
+ }
230
+ hasTableFields() {
231
+ return (this.config.fields?.length ?? 0) > 0;
232
+ }
233
+ isYActive(yFieldId) {
234
+ return this.state?.selectedY === yFieldId;
235
+ }
236
+ isYAggregateActive(yFieldId, aggregateId) {
237
+ return this.state?.selectedY === yFieldId && this.state?.selectedYAggregate === aggregateId;
238
+ }
239
+ emitChange(type, stateUpdate) {
240
+ const event = {
241
+ type,
242
+ state: { ...this.state, ...stateUpdate },
243
+ source: 'user'
244
+ };
245
+ this.viewChange.emit(event);
246
+ }
247
+ 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 }); }
248
+ 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 <!-- 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 : 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\">X \u2014 Axe horizontal</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\">Y \u2014 Axe vertical</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 (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: [".organizer-btn{display:inline-flex;align-items:center;height:34px;border:1px solid #e0e0e0;border-radius:8px;background:#fff;padding:0 10px;cursor:pointer;box-sizing:border-box}.organizer-btn:hover{border-color:#9e9e9e}.organizer-btn-icon{font-size:16px;width:16px;height:16px;line-height:1;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 }); }
249
+ }
250
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: OrganizerButtonComponent, decorators: [{
251
+ type: Component,
252
+ 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 <!-- 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 : 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\">X \u2014 Axe horizontal</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\">Y \u2014 Axe vertical</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 (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: [".organizer-btn{display:inline-flex;align-items:center;height:34px;border:1px solid #e0e0e0;border-radius:8px;background:#fff;padding:0 10px;cursor:pointer;box-sizing:border-box}.organizer-btn:hover{border-color:#9e9e9e}.organizer-btn-icon{font-size:16px;width:16px;height:16px;line-height:1;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"] }]
253
+ }], ctorParameters: function () { return [{ type: i0.ChangeDetectorRef }]; }, propDecorators: { config: [{
254
+ type: Input
255
+ }], state: [{
256
+ type: Input
257
+ }], hideMenuValues: [{
258
+ type: Input
259
+ }], viewChange: [{
260
+ type: Output
261
+ }], sliceStateChange: [{
262
+ type: Output
263
+ }], mainMenuTrigger: [{
264
+ type: ViewChild,
265
+ args: ['mainMenuTrigger']
266
+ }] } });
267
+
268
+ const EMPTY_CATEGORY_KEY = '__empty__';
269
+ const EMPTY_CATEGORY_LABEL = 'Vide';
270
+ const I18N = {
271
+ expandFilterTitle: 'Déplier le filtre',
272
+ collapseFilterTitle: 'Replier le filtre',
273
+ filterPanelTitle: (count) => count > 1 ? 'Filtres' : 'Filtre',
274
+ filterBlockDefaultTitle: 'Filtrer',
275
+ filterEmptyState: 'Aucune donnée',
276
+ };
277
+ class SlicePanelComponent {
278
+ constructor() {
279
+ this.sliceConfigs = [];
280
+ this.columns = [];
281
+ this.data = [];
282
+ this.lazyData = new Map();
283
+ this.lazyStatus = new Map();
284
+ this.showToggle = true;
285
+ this.showCounts = true;
286
+ this.collapsedByDefault = false;
287
+ this.alwaysShow = false;
288
+ this.filterChange = new EventEmitter();
289
+ this.activeKeysChange = new EventEmitter();
290
+ this.dynamicSliceKeysChange = new EventEmitter();
291
+ this.collapsedChange = new EventEmitter();
292
+ this._cdr = inject(ChangeDetectorRef);
293
+ this.i18n = I18N;
294
+ this.isCollapsed = false;
295
+ this.activeKeysBySlice = new Map();
296
+ this._expandedSlices = new Set();
297
+ this._dynamicSlices = [];
298
+ this._cachedSlices = [];
299
+ this._countCache = new Map();
300
+ this.trackBySliceIndex = (index, _) => index;
301
+ this.trackByCategoryKey = (_, cat) => cat.key;
302
+ }
303
+ get hostCollapsed() { return this.isCollapsed; }
304
+ ngOnInit() {
305
+ this.isCollapsed = this.collapsedByDefault;
306
+ }
307
+ ngOnChanges(changes) {
308
+ if (changes['sliceConfigs'] || changes['data'] || changes['columns'] || changes['lazyData'] || changes['lazyStatus']) {
309
+ this._refreshDynamicSlices();
310
+ this._rebuildCache();
311
+ const shouldReemitFilter = !!(changes['data'] || changes['columns'] || changes['lazyData'] || changes['lazyStatus']);
312
+ const hasActiveFilters = [...this.activeKeysBySlice.values()].some(s => s.size > 0);
313
+ if (hasActiveFilters && shouldReemitFilter) {
314
+ this._emitFilter();
315
+ }
316
+ this._cdr.markForCheck();
317
+ }
318
+ }
319
+ get showPanel() {
320
+ const hasCfg = (this.sliceConfigs || []).length > 0 || this._dynamicSlices.length > 0;
321
+ if (this.alwaysShow)
322
+ return hasCfg;
323
+ return hasCfg && (this.data?.length ?? 0) > 0;
324
+ }
325
+ get slices() {
326
+ return this._cachedSlices;
327
+ }
328
+ get staticSliceCount() {
329
+ return (this.sliceConfigs || []).length;
330
+ }
331
+ get staticSlicesForMenu() {
332
+ return (this.sliceConfigs || [])
333
+ .filter((s) => !!s.columnKey)
334
+ .map((s) => ({ key: s.columnKey, title: s.title || s.columnKey }));
335
+ }
336
+ get activeDynamicSliceColumns() {
337
+ return this._dynamicSlices
338
+ .map((d) => (this.columns || []).find((c) => c.key === d.key))
339
+ .filter((c) => !!c);
340
+ }
341
+ get availableColumnsForDynamicSlice() {
342
+ const dynamicKeys = new Set(this._dynamicSlices.map((d) => d.key));
343
+ const staticKeys = new Set((this.sliceConfigs || []).map((s) => s.columnKey).filter((k) => !!k));
344
+ const existingKeys = new Set([...dynamicKeys, ...staticKeys]);
345
+ const seen = new Set();
346
+ return (this.columns || []).filter((c) => {
347
+ if (existingKeys.has(c.key) || seen.has(c.key))
348
+ return false;
349
+ seen.add(c.key);
350
+ return true;
351
+ });
352
+ }
353
+ isSliceCollapsed(sliceIndex) {
354
+ if (this._cachedSlices.length === 1) {
355
+ return this._expandedSlices.has(-1 - sliceIndex);
356
+ }
357
+ return !this._expandedSlices.has(sliceIndex);
358
+ }
359
+ toggleSliceCollapse(sliceIndex) {
360
+ if (this.slices.length === 1) {
361
+ const closedKey = -1 - sliceIndex;
362
+ if (this._expandedSlices.has(closedKey)) {
363
+ this._expandedSlices.delete(closedKey);
364
+ }
365
+ else {
366
+ this._expandedSlices.add(closedKey);
367
+ this._scrollSliceTitleIntoView(sliceIndex);
368
+ }
369
+ }
370
+ else {
371
+ if (this._expandedSlices.has(sliceIndex)) {
372
+ this._expandedSlices.delete(sliceIndex);
373
+ }
374
+ else {
375
+ this._expandedSlices.clear();
376
+ this._expandedSlices.add(sliceIndex);
377
+ this._scrollSliceTitleIntoView(sliceIndex);
378
+ }
379
+ }
380
+ this._cdr.markForCheck();
381
+ }
382
+ togglePanel() {
383
+ this.isCollapsed = !this.isCollapsed;
384
+ this.collapsedChange.emit(this.isCollapsed);
385
+ this._cdr.markForCheck();
386
+ }
387
+ isSliceDynamic(sliceIndex) {
388
+ return sliceIndex >= this.staticSliceCount;
389
+ }
390
+ isSliceLoading(sliceIndex) {
391
+ const staticSlice = (this.sliceConfigs || [])[sliceIndex];
392
+ if (staticSlice?.columnKey) {
393
+ const colDef = (this.columns || []).find(c => c.key === staticSlice.columnKey);
394
+ if (colDef?.lazy) {
395
+ const status = this.lazyStatus.get(staticSlice.columnKey) ?? 'idle';
396
+ return status === 'loading' || status === 'idle';
397
+ }
398
+ return false;
399
+ }
400
+ const key = this._getDynamicSliceColumnKey(sliceIndex);
401
+ if (!key)
402
+ return false;
403
+ const colDef = (this.columns || []).find(c => c.key === key);
404
+ if (!colDef?.lazy)
405
+ return false;
406
+ const status = this.lazyStatus.get(key) ?? 'idle';
407
+ return status === 'loading' || status === 'idle';
408
+ }
409
+ isStaticSliceVisible(_columnKey) {
410
+ return true;
411
+ }
412
+ getSliceIcon(slice) {
413
+ if (slice.icon)
414
+ return slice.icon;
415
+ if (slice.columnKey) {
416
+ return (this.columns || []).find(c => c.key === slice.columnKey)?.icon;
417
+ }
418
+ return undefined;
419
+ }
420
+ isSliceCategoryActive(sliceIndex, categoryKey) {
421
+ return this.activeKeysBySlice.get(sliceIndex)?.has(categoryKey) ?? false;
422
+ }
423
+ isSliceAllActive(sliceIndex) {
424
+ return !this.activeKeysBySlice.has(sliceIndex) || this.activeKeysBySlice.get(sliceIndex).size === 0;
425
+ }
426
+ sliceCategoryCount(sliceIndex, categoryKey) {
427
+ const cacheKey = `${sliceIndex}:${categoryKey}`;
428
+ if (this._countCache.has(cacheKey)) {
429
+ return this._countCache.get(cacheKey);
430
+ }
431
+ const slice = this._cachedSlices[sliceIndex];
432
+ if (!slice)
433
+ return 0;
434
+ const category = (slice.categories || []).find((c) => c.key === categoryKey);
435
+ const count = category ? (this.data || []).filter((row) => category.filter ? category.filter(row) : true).length : 0;
436
+ this._countCache.set(cacheKey, count);
437
+ return count;
438
+ }
439
+ isSliceEmpty(sliceIndex) {
440
+ const slice = this._cachedSlices[sliceIndex];
441
+ if (!slice)
442
+ return true;
443
+ return (slice.categories || []).every(c => this.sliceCategoryCount(sliceIndex, c.key) === 0);
444
+ }
445
+ onSliceCategorySelected(sliceIndex, categoryKey) {
446
+ const slice = this.slices[sliceIndex];
447
+ if (!slice)
448
+ return;
449
+ if (!this.activeKeysBySlice.has(sliceIndex)) {
450
+ this.activeKeysBySlice.set(sliceIndex, new Set());
451
+ }
452
+ const activeKeys = this.activeKeysBySlice.get(sliceIndex);
453
+ if (slice.multiSelect === false) {
454
+ if (activeKeys.has(categoryKey)) {
455
+ activeKeys.clear();
456
+ }
457
+ else {
458
+ activeKeys.clear();
459
+ activeKeys.add(categoryKey);
460
+ }
461
+ }
462
+ else {
463
+ if (activeKeys.has(categoryKey)) {
464
+ activeKeys.delete(categoryKey);
465
+ }
466
+ else {
467
+ activeKeys.add(categoryKey);
468
+ }
469
+ }
470
+ this._emitFilter();
471
+ this._cdr.markForCheck();
472
+ }
473
+ onSliceAllSelected(sliceIndex) {
474
+ if (this.activeKeysBySlice.has(sliceIndex)) {
475
+ this.activeKeysBySlice.get(sliceIndex).clear();
476
+ }
477
+ this._emitFilter();
478
+ this._cdr.markForCheck();
479
+ }
480
+ getActiveFilters() {
481
+ const result = {};
482
+ this._cachedSlices.forEach((_, idx) => {
483
+ const keys = this.activeKeysBySlice.get(idx);
484
+ result[idx] = keys ? [...keys] : [];
485
+ });
486
+ return result;
487
+ }
488
+ restoreFilters(filters) {
489
+ this.activeKeysBySlice = new Map(Object.entries(filters).map(([idx, keys]) => [Number(idx), new Set(keys)]));
490
+ this._emitFilter();
491
+ this._cdr.markForCheck();
492
+ }
493
+ addDynamicSlice(column) {
494
+ const distinctValues = this._computeDistinctValues(column.key);
495
+ const newSlice = {
496
+ title: column.header,
497
+ columnKey: column.key,
498
+ categories: distinctValues.map((v) => this._buildCategoryFromValue(column.key, v)),
499
+ };
500
+ this._dynamicSlices = [...this._dynamicSlices, { key: column.key, slice: newSlice }];
501
+ this._rebuildCache();
502
+ this.dynamicSliceKeysChange.emit(this._dynamicSlices.map(d => d.key));
503
+ this._emitFilter();
504
+ this._cdr.markForCheck();
505
+ }
506
+ removeDynamicSlice(sliceIndex) {
507
+ const dynamicIndex = sliceIndex - this.staticSliceCount;
508
+ if (dynamicIndex < 0)
509
+ return;
510
+ const newActiveKeys = new Map();
511
+ this.activeKeysBySlice.forEach((keys, idx) => {
512
+ if (idx < sliceIndex)
513
+ newActiveKeys.set(idx, keys);
514
+ else if (idx > sliceIndex)
515
+ newActiveKeys.set(idx - 1, keys);
516
+ });
517
+ this.activeKeysBySlice = newActiveKeys;
518
+ this._dynamicSlices = this._dynamicSlices.filter((_, i) => i !== dynamicIndex);
519
+ this._rebuildCache();
520
+ this.dynamicSliceKeysChange.emit(this._dynamicSlices.map(d => d.key));
521
+ this._emitFilter();
522
+ this._cdr.markForCheck();
523
+ }
524
+ removeDynamicSliceByKey(key) {
525
+ const dynamicIndex = this._dynamicSlices.findIndex((d) => d.key === key);
526
+ if (dynamicIndex < 0)
527
+ return;
528
+ const sliceIndex = this.staticSliceCount + dynamicIndex;
529
+ this.removeDynamicSlice(sliceIndex);
530
+ }
531
+ _emitFilter() {
532
+ const slices = this.slices;
533
+ const activeKeysBySlice = this.activeKeysBySlice;
534
+ this.activeKeysChange.emit(slices.map((_, i) => {
535
+ const keys = activeKeysBySlice.get(i);
536
+ return keys ? [...keys] : [];
537
+ }));
538
+ const pred = (row) => slices.every((slice, sliceIndex) => {
539
+ const activeKeys = activeKeysBySlice.get(sliceIndex);
540
+ if (!activeKeys || activeKeys.size === 0)
541
+ return true;
542
+ if (!slice.categories || slice.categories.length === 0)
543
+ return true;
544
+ return [...activeKeys].some((key) => {
545
+ const category = (slice.categories || []).find((c) => c.key === key);
546
+ if (!category)
547
+ return false;
548
+ if (!category?.filter)
549
+ return true;
550
+ return category.filter(row);
551
+ });
552
+ });
553
+ this.filterChange.emit(pred);
554
+ }
555
+ _getDynamicSliceColumnKey(sliceIndex) {
556
+ const dynamicIndex = sliceIndex - this.staticSliceCount;
557
+ return this._dynamicSlices[dynamicIndex]?.key ?? null;
558
+ }
559
+ _getSliceIdentity(slice, fallbackIndex) {
560
+ if (slice.columnKey)
561
+ return `column:${slice.columnKey}`;
562
+ if (slice.title)
563
+ return `title:${slice.title}`;
564
+ const categoryKeys = (slice.categories || []).map((c) => c.key).join('|');
565
+ return `index:${fallbackIndex}|categories:${categoryKeys}`;
566
+ }
567
+ _materializeSlice(slice) {
568
+ if (!slice.columnKey || (slice.categories && slice.categories.length > 0)) {
569
+ return slice;
570
+ }
571
+ const col = (this.columns || []).find(c => c.key === slice.columnKey);
572
+ const isLazy = !!col?.lazy;
573
+ if (isLazy && this.lazyStatus.get(slice.columnKey) !== 'loaded') {
574
+ return { ...slice, categories: [] };
575
+ }
576
+ if (slice.bucket) {
577
+ const distinctValues = [...new Set((this.data || []).map(slice.bucket))].sort();
578
+ return {
579
+ ...slice,
580
+ categories: distinctValues.map((v) => ({
581
+ key: v, label: v,
582
+ filter: (row) => slice.bucket(row) === v,
583
+ })),
584
+ };
585
+ }
586
+ const distinctValues = this._computeDistinctValues(slice.columnKey);
587
+ return {
588
+ ...slice,
589
+ categories: distinctValues.map((v) => this._buildCategoryFromValue(slice.columnKey, v)),
590
+ };
591
+ }
592
+ _buildCategoryFromValue(columnKey, v) {
593
+ if (v === EMPTY_CATEGORY_KEY) {
594
+ return {
595
+ key: EMPTY_CATEGORY_KEY,
596
+ label: EMPTY_CATEGORY_LABEL,
597
+ filter: (row) => {
598
+ const val = this._getLazyAwareValue(columnKey, row);
599
+ return val == null || val === '';
600
+ },
601
+ };
602
+ }
603
+ return {
604
+ key: v, label: v,
605
+ filter: (row) => String(this._getLazyAwareValue(columnKey, row) ?? '') === v,
606
+ };
607
+ }
608
+ _computeDistinctValues(columnKey) {
609
+ const rawValues = (this.data || []).map((row) => {
610
+ const val = this._getLazyAwareValue(columnKey, row);
611
+ return val != null && val !== '' ? String(val) : null;
612
+ });
613
+ const distinct = [...new Set(rawValues.filter((v) => v !== null))].sort();
614
+ if (rawValues.some(v => v === null))
615
+ distinct.push(EMPTY_CATEGORY_KEY);
616
+ return distinct;
617
+ }
618
+ _getLazyAwareValue(columnKey, row) {
619
+ const colDef = (this.columns || []).find(c => c.key === columnKey);
620
+ if (colDef?.lazy) {
621
+ return this.lazyData.get(columnKey)?.get(row) ?? null;
622
+ }
623
+ if (colDef?.value) {
624
+ return colDef.value(row, 0);
625
+ }
626
+ return row[columnKey];
627
+ }
628
+ _rebuildCache() {
629
+ const previousSlices = this._cachedSlices;
630
+ const previousActiveKeys = this.activeKeysBySlice;
631
+ const allSlices = [...(this.sliceConfigs || []), ...this._dynamicSlices.map((d) => d.slice)];
632
+ this._cachedSlices = allSlices.map((slice) => this._materializeSlice(slice));
633
+ const previousKeysByIdentity = new Map();
634
+ previousSlices.forEach((slice, index) => {
635
+ const activeKeys = previousActiveKeys.get(index);
636
+ if (!activeKeys || activeKeys.size === 0)
637
+ return;
638
+ previousKeysByIdentity.set(this._getSliceIdentity(slice, index), new Set(activeKeys));
639
+ });
640
+ const nextActiveKeys = new Map();
641
+ this._cachedSlices.forEach((slice, index) => {
642
+ const previousKeys = previousKeysByIdentity.get(this._getSliceIdentity(slice, index));
643
+ if (!previousKeys || previousKeys.size === 0)
644
+ return;
645
+ const availableKeys = new Set((slice.categories || []).map((c) => c.key));
646
+ const matchingKeys = [...previousKeys].filter((key) => availableKeys.has(key));
647
+ if (matchingKeys.length > 0)
648
+ nextActiveKeys.set(index, new Set(matchingKeys));
649
+ });
650
+ this.activeKeysBySlice = nextActiveKeys;
651
+ this._countCache.clear();
652
+ }
653
+ _refreshDynamicSlices() {
654
+ this._dynamicSlices = this._dynamicSlices.map(({ key, slice }) => {
655
+ const distinctValues = this._computeDistinctValues(key);
656
+ return {
657
+ key,
658
+ slice: {
659
+ ...slice,
660
+ categories: distinctValues.map((v) => this._buildCategoryFromValue(key, v)),
661
+ },
662
+ };
663
+ });
664
+ }
665
+ _scrollSliceTitleIntoView(sliceIndex) {
666
+ setTimeout(() => {
667
+ const body = this.slicePanelBodyRef?.nativeElement;
668
+ if (!body)
669
+ return;
670
+ const el = body.querySelector(`[data-slice-index="${sliceIndex}"]`);
671
+ el?.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
672
+ }, 50);
673
+ }
674
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SlicePanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
675
+ 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: [":host{display:block;height:100%;min-height:0;box-sizing:border-box}.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 }); }
676
+ }
677
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SlicePanelComponent, decorators: [{
678
+ type: Component,
679
+ 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 <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: [":host{display:block;height:100%;min-height:0;box-sizing:border-box}.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"] }]
680
+ }], propDecorators: { sliceConfigs: [{
681
+ type: Input
682
+ }], columns: [{
683
+ type: Input
684
+ }], data: [{
685
+ type: Input
686
+ }], lazyData: [{
687
+ type: Input
688
+ }], lazyStatus: [{
689
+ type: Input
690
+ }], showToggle: [{
691
+ type: Input
692
+ }], showCounts: [{
693
+ type: Input
694
+ }], collapsedByDefault: [{
695
+ type: Input
696
+ }], alwaysShow: [{
697
+ type: Input
698
+ }], filterChange: [{
699
+ type: Output
700
+ }], activeKeysChange: [{
701
+ type: Output
702
+ }], dynamicSliceKeysChange: [{
703
+ type: Output
704
+ }], collapsedChange: [{
705
+ type: Output
706
+ }], slicePanelBodyRef: [{
707
+ type: ViewChild,
708
+ args: ['slicePanelBody']
709
+ }], hostCollapsed: [{
710
+ type: HostBinding,
711
+ args: ['class.collapsed']
712
+ }] } });
713
+
714
+ class OrganizerButtonModule {
715
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: OrganizerButtonModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
716
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "16.2.12", ngImport: i0, type: OrganizerButtonModule, imports: [OrganizerButtonComponent, SlicePanelComponent], exports: [OrganizerButtonComponent, SlicePanelComponent] }); }
717
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: OrganizerButtonModule, imports: [OrganizerButtonComponent, SlicePanelComponent] }); }
718
+ }
719
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: OrganizerButtonModule, decorators: [{
720
+ type: NgModule,
721
+ args: [{
722
+ imports: [OrganizerButtonComponent, SlicePanelComponent],
723
+ exports: [OrganizerButtonComponent, SlicePanelComponent]
724
+ }]
725
+ }] });
726
+
727
+ const DEFAULT_MENU_LABELS = {
728
+ 'fields': 'Champs',
729
+ 'champs': 'Champs',
730
+ 'groups': 'Grouper par',
731
+ 'slices': 'Filtrer par',
732
+ 'templates': 'Template',
733
+ 'custom': 'Options'
734
+ };
735
+ const DEFAULT_MENU_ICONS = {
736
+ 'fields': 'visibility',
737
+ 'champs': 'bar_chart',
738
+ 'groups': 'category',
739
+ 'slices': 'filter_list',
740
+ 'templates': 'dashboard_customize',
741
+ 'custom': 'settings'
742
+ };
743
+
744
+ function buildYFields(indicators) {
745
+ const ordered = [];
746
+ const map = new Map();
747
+ for (const indicator of indicators) {
748
+ const groupKey = indicator.group;
749
+ if (groupKey) {
750
+ if (!map.has(groupKey)) {
751
+ ordered.push(groupKey);
752
+ map.set(groupKey, { id: groupKey, label: groupKey, aggregates: [] });
753
+ }
754
+ const parent = map.get(groupKey);
755
+ if (parent?.aggregates) {
756
+ parent.aggregates.push({ id: indicator.id, label: indicator.label });
757
+ }
758
+ }
759
+ else {
760
+ ordered.push(indicator.id);
761
+ map.set(indicator.id, { id: indicator.id, label: indicator.label });
762
+ }
763
+ }
764
+ return ordered.map(k => map.get(k)).filter((f) => !!f);
765
+ }
766
+ function resolveYKey(selectedY, selectedYAggregate) {
767
+ return selectedYAggregate ?? selectedY;
768
+ }
769
+
770
+ function chartConfigToOrganizer(chartConfig, options = {}) {
771
+ if (!chartConfig)
772
+ return {};
773
+ const { onFetchSliceData, onExportVisual, onExportData, switchView } = options;
774
+ const activeIndicator = chartConfig.indicators?.items?.find(i => i.selected);
775
+ const baseXFields = (chartConfig.groups?.items || [])
776
+ .filter(g => !_isSynthetic(g))
777
+ .map(g => ({ id: g.key, label: g.menu.label || g.key }));
778
+ const yFields = buildYFields((chartConfig.indicators?.items || []).map(i => ({ id: i.key, label: i.menu.label || i.key, group: i.group })));
779
+ const stackFields = (activeIndicator?.extra?.stacks?.items || [])
780
+ .filter((s) => !_isSynthetic(s))
781
+ .map((s) => ({ id: s.key, label: s.menu.label || s.key }));
782
+ const seenKeys = new Set();
783
+ const mergedGroupFields = [...baseXFields, ...stackFields].filter(f => {
784
+ if (seenKeys.has(f.id))
785
+ return false;
786
+ seenKeys.add(f.id);
787
+ return true;
788
+ });
789
+ return {
790
+ xFields: mergedGroupFields,
791
+ yFields,
792
+ groups: mergedGroupFields,
793
+ slices: (chartConfig.filters?.items || []).map(f => ({ id: f.key, label: f.menu.label || f.key })),
794
+ showReset: false,
795
+ onFetchSliceData,
796
+ showExport: !!(onExportVisual || onExportData),
797
+ onExportVisual,
798
+ onExportData,
799
+ switchView,
800
+ };
801
+ }
802
+ function chartConfigToState(chartConfig) {
803
+ if (!chartConfig)
804
+ return {};
805
+ const activeIndicator = chartConfig.indicators?.items?.find(i => i.selected);
806
+ const activeStack = (activeIndicator?.extra?.stacks?.items || []).find((s) => s.selected);
807
+ const activeGroup = chartConfig.groups?.items?.find(g => g.selected);
808
+ const activeSlices = (chartConfig.filters?.items || []).filter(f => f.selected);
809
+ const indicatorGroup = activeIndicator?.group;
810
+ return {
811
+ selectedX: activeGroup?.key,
812
+ selectedY: indicatorGroup ?? activeIndicator?.key,
813
+ selectedYAggregate: indicatorGroup ? activeIndicator?.key : undefined,
814
+ selectedGroupBy: activeStack?.key,
815
+ selectedSlices: activeSlices.map(f => f.key),
816
+ };
817
+ }
818
+ function chartConfigToUnifiedState(chartConfig, viewMode = 'chart') {
819
+ const state = chartConfigToState(chartConfig);
820
+ return {
821
+ viewMode,
822
+ dimension: state.selectedX,
823
+ metric: resolveYKey(state.selectedY, state.selectedYAggregate),
824
+ metricAggregate: state.selectedYAggregate,
825
+ breakdown: state.selectedGroupBy,
826
+ filters: state.selectedSlices,
827
+ };
828
+ }
829
+ function applyOrganizerEventToChart(event, chartConfig) {
830
+ if (!chartConfig)
831
+ return;
832
+ const state = event.state;
833
+ _cleanSynthetics(chartConfig);
834
+ if (state.selectedX !== undefined)
835
+ _applyXSelection(state.selectedX, chartConfig);
836
+ if (state.selectedY !== undefined)
837
+ _applyYSelection(state.selectedY, state.selectedYAggregate, chartConfig);
838
+ const effectiveGroupBy = state.selectedGroupBy !== state.selectedX ? state.selectedGroupBy : undefined;
839
+ _applyGroupBySelection(effectiveGroupBy, chartConfig);
840
+ if (state.selectedSlices !== undefined)
841
+ _applySliceSelection(state.selectedSlices, chartConfig);
842
+ event.resolvedYUnit = resolveYUnit(chartConfig, event.state.selectedY, event.state.selectedYAggregate);
843
+ }
844
+ function resolveYUnit(chartConfig, selectedY, selectedYAggregate) {
845
+ if (!chartConfig || !selectedY)
846
+ return undefined;
847
+ const actualKey = resolveYKey(selectedY, selectedYAggregate);
848
+ return chartConfig.indicators?.items?.find(i => i.key === actualKey)?.unit;
849
+ }
850
+ const _syntheticItems = new WeakSet();
851
+ function _makeSynthetic(item) {
852
+ _syntheticItems.add(item);
853
+ return item;
854
+ }
855
+ function _isSynthetic(item) {
856
+ return _syntheticItems.has(item);
857
+ }
858
+ function _cleanSynthetics(chartConfig) {
859
+ chartConfig.groups.items = chartConfig.groups.items.filter(g => !_isSynthetic(g));
860
+ chartConfig.indicators.items.forEach(ind => {
861
+ if (ind.extra?.stacks?.items) {
862
+ ind.extra.stacks.items = ind.extra.stacks.items.filter((s) => !_isSynthetic(s));
863
+ }
864
+ });
865
+ }
866
+ function _applyXSelection(selectedX, chartConfig) {
867
+ const isGroupItem = chartConfig.groups.items.some(g => g.key === selectedX);
868
+ if (isGroupItem) {
869
+ chartConfig.groups.items.forEach(g => { g.selected = g.key === selectedX; });
870
+ return;
871
+ }
872
+ const allStacks = chartConfig.indicators.items.flatMap(i => i.extra?.stacks?.items ?? []);
873
+ const stackItem = allStacks.find(s => s.key === selectedX);
874
+ if (stackItem) {
875
+ chartConfig.groups.items.forEach(g => { g.selected = false; });
876
+ chartConfig.groups.items.push(_makeSynthetic({
877
+ key: stackItem.key,
878
+ selected: true,
879
+ menu: stackItem.menu,
880
+ jquery: stackItem.jquery
881
+ ? { value: () => stackItem.jquery.value(), buildAlias: () => stackItem.jquery.buildAlias() }
882
+ : undefined,
883
+ }));
884
+ }
885
+ }
886
+ function _applyYSelection(selectedY, selectedYAggregate, chartConfig) {
887
+ const actualKey = resolveYKey(selectedY, selectedYAggregate);
888
+ chartConfig.indicators.items.forEach(i => { i.selected = i.key === actualKey; });
889
+ chartConfig.indicators.items.forEach(i => {
890
+ i.extra?.stacks?.items?.forEach((s) => { s.selected = false; });
891
+ });
892
+ }
893
+ function _applyGroupBySelection(effectiveGroupBy, chartConfig) {
894
+ const activeIndicator = chartConfig.indicators.items.find(i => i.selected);
895
+ const stackItems = activeIndicator?.extra?.stacks?.items ?? [];
896
+ if (effectiveGroupBy === undefined) {
897
+ stackItems.forEach(s => { s.selected = false; });
898
+ return;
899
+ }
900
+ const isStackItem = stackItems.some(s => s.key === effectiveGroupBy);
901
+ if (isStackItem) {
902
+ stackItems.forEach(s => { s.selected = s.key === effectiveGroupBy; });
903
+ return;
904
+ }
905
+ const groupItem = chartConfig.groups.items.find(g => g.key === effectiveGroupBy);
906
+ if (groupItem && activeIndicator) {
907
+ stackItems.forEach(s => { s.selected = false; });
908
+ if (!activeIndicator.extra) {
909
+ activeIndicator.extra = { stacks: { optional: true, items: [] } };
910
+ }
911
+ else if (!activeIndicator.extra.stacks) {
912
+ activeIndicator.extra.stacks = { optional: true, items: [] };
913
+ }
914
+ activeIndicator.extra.stacks.items.push(_makeSynthetic({
915
+ key: groupItem.key,
916
+ selected: true,
917
+ menu: groupItem.menu,
918
+ jquery: groupItem.jquery ? {
919
+ value: () => groupItem.jquery.value(),
920
+ buildAlias: () => groupItem.jquery.buildAlias(),
921
+ buildName: (_item, value) => String(value),
922
+ } : undefined,
923
+ }));
924
+ }
925
+ else {
926
+ stackItems.forEach(s => { s.selected = false; });
927
+ }
928
+ }
929
+ function _applySliceSelection(selectedSlices, chartConfig) {
930
+ chartConfig.filters?.items?.forEach(f => { f.selected = selectedSlices.includes(f.key); });
931
+ }
932
+
933
+ function humanizeKey(key) {
934
+ return key
935
+ .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
936
+ .replace(/[_-]+/g, ' ')
937
+ .replace(/^./, (v) => v.toUpperCase());
938
+ }
939
+ class OrganizerFacade {
940
+ constructor() {
941
+ this.fields = {
942
+ activeFields: [],
943
+ userCustomized: false,
944
+ };
945
+ this.groupBy = {
946
+ activeKey: null,
947
+ userCustomized: false,
948
+ };
949
+ this.sliceBy = {
950
+ activeDynamicFields: [],
951
+ availableForDynamic: [],
952
+ allDynamicFields: [],
953
+ activeLabel: 'Aucun',
954
+ };
955
+ this._panelConfig = { fields: [], sliceConfigs: [] };
956
+ this._hiddenStaticKeys = new Set();
957
+ this._events$ = new Subject();
958
+ this.events$ = this._events$.asObservable();
959
+ }
960
+ get enabled() {
961
+ return this._panelConfig.config?.enabled === true;
962
+ }
963
+ get showFields() {
964
+ return this.enabled;
965
+ }
966
+ get showGroupBySection() {
967
+ return this.enabled && this.groupByFields.length > 0;
968
+ }
969
+ get showSliceBySection() {
970
+ return this.enabled && ((this._panelConfig.sliceConfigs?.length ?? 0) > 0 ||
971
+ this.sliceBy.allDynamicFields.length > 0);
972
+ }
973
+ get allowFieldRemoval() {
974
+ return this._panelConfig.config?.enableFieldRemoval !== false;
975
+ }
976
+ get isFieldDragDropEnabled() {
977
+ return this._panelConfig.config?.enableFieldDragDrop === true;
978
+ }
979
+ get menuBaseFields() {
980
+ return this._uniqueByKey((this._panelConfig.fields || []).filter(c => !c.optional));
981
+ }
982
+ get menuOptionalFields() {
983
+ return this._uniqueByKey((this._panelConfig.fields || []).filter(c => c.optional));
984
+ }
985
+ get totalFieldCount() {
986
+ return new Set((this._panelConfig.fields || []).map(c => c.key)).size;
987
+ }
988
+ get groupByFields() {
989
+ const seen = new Set();
990
+ return (this._panelConfig.fields || []).filter(c => {
991
+ if (c.groupable === false || seen.has(c.key))
992
+ return false;
993
+ if (!c.header && c.groupable !== true)
994
+ return false;
995
+ seen.add(c.key);
996
+ return true;
997
+ });
998
+ }
999
+ get activeGroupByLabel() {
1000
+ if (!this.groupBy.activeKey)
1001
+ return this._panelConfig.noneLabel ?? 'Aucun';
1002
+ const field = this.groupByFields.find(c => c.key === this.groupBy.activeKey);
1003
+ return field?.header || this.groupBy.activeKey;
1004
+ }
1005
+ get staticSlicesForMenu() {
1006
+ return (this._panelConfig.sliceConfigs ?? [])
1007
+ .filter(s => !!s.columnKey)
1008
+ .map(s => {
1009
+ const fieldIcon = (this._panelConfig.fields ?? []).find(c => c.key === s.columnKey)?.icon;
1010
+ return { key: s.columnKey, title: s.title || humanizeKey(s.columnKey), icon: s.icon ?? fieldIcon };
1011
+ });
1012
+ }
1013
+ fieldLabel(field) {
1014
+ return field.header || humanizeKey(field.key);
1015
+ }
1016
+ update(panelConfig) {
1017
+ this._panelConfig = panelConfig;
1018
+ this._refreshDefaultFields();
1019
+ this._refreshDynamicSliceMeta();
1020
+ if (!this.groupBy.userCustomized) {
1021
+ this.groupBy.activeKey = panelConfig.defaultGroupBy ?? null;
1022
+ }
1023
+ }
1024
+ setActiveFields(fields) {
1025
+ this.fields.activeFields = fields;
1026
+ this.fields.userCustomized = true;
1027
+ }
1028
+ setUserCustomized(value) {
1029
+ this.fields.userCustomized = value;
1030
+ }
1031
+ resetToDefaults() {
1032
+ this.fields.userCustomized = false;
1033
+ this.groupBy.userCustomized = false;
1034
+ this._refreshDefaultFields();
1035
+ this.groupBy.activeKey = this._panelConfig.defaultGroupBy ?? null;
1036
+ this.sliceBy.activeDynamicFields = [];
1037
+ this._refreshDynamicSliceMeta();
1038
+ }
1039
+ addField(field) {
1040
+ const configFields = this._panelConfig.fields ?? [];
1041
+ const configIndex = configFields.findIndex(c => c.key === field.key);
1042
+ if (configIndex === -1) {
1043
+ this.fields.activeFields = [...this.fields.activeFields, field];
1044
+ }
1045
+ else {
1046
+ const insertBefore = this.fields.activeFields.findIndex(c => {
1047
+ const ci = configFields.findIndex(cc => cc.key === c.key);
1048
+ return ci !== -1 && ci > configIndex;
1049
+ });
1050
+ if (insertBefore === -1) {
1051
+ this.fields.activeFields = [...this.fields.activeFields, field];
1052
+ }
1053
+ else {
1054
+ const copy = [...this.fields.activeFields];
1055
+ copy.splice(insertBefore, 0, field);
1056
+ this.fields.activeFields = copy;
1057
+ }
1058
+ }
1059
+ this.fields.userCustomized = true;
1060
+ this._events$.next({ type: 'fieldsChanged', fieldIds: this.fields.activeFields.map(c => c.key) });
1061
+ }
1062
+ removeField(fieldKey) {
1063
+ const field = this.fields.activeFields.find(c => c.key === fieldKey);
1064
+ if (!field)
1065
+ return false;
1066
+ if (!field.optional && !this.allowFieldRemoval)
1067
+ return false;
1068
+ if (this.fields.activeFields.length <= 1)
1069
+ return false;
1070
+ this.fields.activeFields = this.fields.activeFields.filter(c => c.key !== fieldKey);
1071
+ this.fields.userCustomized = true;
1072
+ this._events$.next({ type: 'fieldsChanged', fieldIds: this.fields.activeFields.map(c => c.key) });
1073
+ return true;
1074
+ }
1075
+ toggleFieldVisibility(field, visible) {
1076
+ const isVisible = this.isFieldVisible(field.key);
1077
+ if (visible && !isVisible) {
1078
+ this.addField(field);
1079
+ }
1080
+ else if (!visible && isVisible) {
1081
+ this.removeField(field.key);
1082
+ }
1083
+ }
1084
+ reorderFields(fromKey, toKey) {
1085
+ const prev = this.fields.activeFields.findIndex(c => c.key === fromKey);
1086
+ const curr = this.fields.activeFields.findIndex(c => c.key === toKey);
1087
+ if (prev === -1 || curr === -1 || prev === curr)
1088
+ return false;
1089
+ const reordered = [...this.fields.activeFields];
1090
+ const [moved] = reordered.splice(prev, 1);
1091
+ reordered.splice(curr, 0, moved);
1092
+ this.fields.activeFields = reordered;
1093
+ this.fields.userCustomized = true;
1094
+ this._events$.next({ type: 'fieldsChanged', fieldIds: this.fields.activeFields.map(c => c.key) });
1095
+ return true;
1096
+ }
1097
+ isFieldVisible(key) {
1098
+ return this.fields.activeFields.some(c => c.key === key);
1099
+ }
1100
+ setGroupBy(key) {
1101
+ this.groupBy.activeKey = key;
1102
+ this.groupBy.userCustomized = true;
1103
+ this._events$.next({ type: 'groupByChanged', key });
1104
+ }
1105
+ onDynamicSliceKeysChange(keys) {
1106
+ const allFields = this._panelConfig.fields ?? [];
1107
+ const staticKeys = new Set((this._panelConfig.sliceConfigs ?? []).map(s => s.columnKey).filter((k) => !!k));
1108
+ const dynamicKeySet = new Set(keys);
1109
+ const existingKeys = new Set([...staticKeys, ...dynamicKeySet]);
1110
+ this.sliceBy.activeDynamicFields = keys
1111
+ .map(k => allFields.find(c => c.key === k))
1112
+ .filter((c) => !!c);
1113
+ const seen = new Set();
1114
+ this.sliceBy.availableForDynamic = allFields.filter(c => {
1115
+ if (c.sliceable === false || existingKeys.has(c.key) || seen.has(c.key))
1116
+ return false;
1117
+ seen.add(c.key);
1118
+ return true;
1119
+ });
1120
+ this._computeSliceLabel();
1121
+ this._events$.next({ type: 'dynamicSlicesChanged', keys });
1122
+ }
1123
+ isDynamicSliceActive(key) {
1124
+ return this.sliceBy.activeDynamicFields.some(c => c.key === key);
1125
+ }
1126
+ toOrganizerState() {
1127
+ return {
1128
+ selectedFieldIds: this.fields.activeFields.map(c => c.key),
1129
+ groupByKey: this.groupBy.activeKey,
1130
+ dynamicSliceKeys: this.sliceBy.activeDynamicFields.map(c => c.key),
1131
+ };
1132
+ }
1133
+ destroy() { this._events$.complete(); }
1134
+ updateHiddenStaticKeys(keys) {
1135
+ this._hiddenStaticKeys = keys;
1136
+ this._computeSliceLabel();
1137
+ }
1138
+ _refreshDefaultFields() {
1139
+ const defaultFields = (this._panelConfig.fields || []).filter(c => !c.optional);
1140
+ if (!this.fields.userCustomized || this.fields.activeFields.length === 0) {
1141
+ this.fields.activeFields = [...defaultFields];
1142
+ }
1143
+ else {
1144
+ const allowedKeys = new Set((this._panelConfig.fields || []).map(c => c.key));
1145
+ this.fields.activeFields = this.fields.activeFields.filter(c => allowedKeys.has(c.key));
1146
+ }
1147
+ }
1148
+ _refreshDynamicSliceMeta() {
1149
+ const allFields = this._panelConfig.fields ?? [];
1150
+ const staticKeys = new Set((this._panelConfig.sliceConfigs ?? []).map(s => s.columnKey).filter((k) => !!k));
1151
+ const activeDynKeys = new Set(this.sliceBy.activeDynamicFields.map(c => c.key));
1152
+ const seen1 = new Set();
1153
+ this.sliceBy.availableForDynamic = allFields.filter(c => {
1154
+ if (c.sliceable === false || staticKeys.has(c.key) || activeDynKeys.has(c.key) || seen1.has(c.key))
1155
+ return false;
1156
+ if (!c.header && c.sliceable !== true)
1157
+ return false;
1158
+ seen1.add(c.key);
1159
+ return true;
1160
+ });
1161
+ const seen2 = new Set();
1162
+ this.sliceBy.allDynamicFields = allFields.filter(c => {
1163
+ if (c.sliceable === false || staticKeys.has(c.key) || seen2.has(c.key))
1164
+ return false;
1165
+ if (!c.header && c.sliceable !== true)
1166
+ return false;
1167
+ seen2.add(c.key);
1168
+ return true;
1169
+ });
1170
+ this._computeSliceLabel();
1171
+ }
1172
+ _computeSliceLabel() {
1173
+ const visibleStatics = (this._panelConfig.sliceConfigs ?? []).filter(s => !s.columnKey || !this._hiddenStaticKeys.has(s.columnKey));
1174
+ const total = visibleStatics.length + this.sliceBy.activeDynamicFields.length;
1175
+ if (total === 0) {
1176
+ this.sliceBy.activeLabel = this._panelConfig.noneLabel ?? 'Aucun';
1177
+ return;
1178
+ }
1179
+ if (total === 1) {
1180
+ if (visibleStatics.length === 1) {
1181
+ const first = visibleStatics[0];
1182
+ this.sliceBy.activeLabel = first?.title || first?.columnKey || '1';
1183
+ return;
1184
+ }
1185
+ this.sliceBy.activeLabel = this.sliceBy.activeDynamicFields[0]?.header || '1';
1186
+ return;
1187
+ }
1188
+ this.sliceBy.activeLabel = `${total} actifs`;
1189
+ }
1190
+ _uniqueByKey(fields) {
1191
+ const seen = new Set();
1192
+ return fields.filter(c => {
1193
+ if (seen.has(c.key))
1194
+ return false;
1195
+ seen.add(c.key);
1196
+ return true;
1197
+ });
1198
+ }
1199
+ }
1200
+
1201
+ class GroupByManager {
1202
+ constructor(onNeedProjection, onNeedRender) {
1203
+ this.onNeedProjection = onNeedProjection;
1204
+ this.onNeedRender = onNeedRender;
1205
+ this._collapsedGroups = new Set();
1206
+ this._groupPages = new Map();
1207
+ this.groupPageSize = 5;
1208
+ this.groupSortOrder = 'asc';
1209
+ this._defaultGroupCollapsed = false;
1210
+ }
1211
+ setDefaultCollapsed(collapsed) {
1212
+ this._defaultGroupCollapsed = collapsed;
1213
+ }
1214
+ reset() {
1215
+ this._collapsedGroups.clear();
1216
+ this._groupPages.clear();
1217
+ }
1218
+ isGroupCollapsed(groupKey) {
1219
+ return this._defaultGroupCollapsed
1220
+ ? !this._collapsedGroups.has(groupKey)
1221
+ : this._collapsedGroups.has(groupKey);
1222
+ }
1223
+ toggleGroupCollapse(groupKey, scrollTo) {
1224
+ if (this._collapsedGroups.has(groupKey)) {
1225
+ this._collapsedGroups.delete(groupKey);
1226
+ }
1227
+ else {
1228
+ this._collapsedGroups.clear();
1229
+ this._collapsedGroups.add(groupKey);
1230
+ scrollTo(groupKey);
1231
+ }
1232
+ this.onNeedProjection();
1233
+ }
1234
+ getSingleOpenGroup() {
1235
+ return this._defaultGroupCollapsed && this._collapsedGroups.size === 1
1236
+ ? [...this._collapsedGroups][0]
1237
+ : null;
1238
+ }
1239
+ getPage(groupKey) {
1240
+ return this._groupPages.get(groupKey) ?? 0;
1241
+ }
1242
+ groupPageBack(groupKey) {
1243
+ const current = this._groupPages.get(groupKey) ?? 0;
1244
+ if (current > 0) {
1245
+ this._groupPages.set(groupKey, current - 1);
1246
+ this.onNeedProjection();
1247
+ }
1248
+ }
1249
+ groupPageForward(groupKey, totalCount) {
1250
+ const maxPage = Math.ceil(totalCount / this.groupPageSize) - 1;
1251
+ const current = this._groupPages.get(groupKey) ?? 0;
1252
+ if (current < maxPage) {
1253
+ this._groupPages.set(groupKey, current + 1);
1254
+ this.onNeedProjection();
1255
+ }
1256
+ }
1257
+ onGroupPageSizeChange(size) {
1258
+ this.groupPageSize = size;
1259
+ this._groupPages.clear();
1260
+ this.onNeedRender();
1261
+ }
1262
+ onGroupSortToggle(dir, scrollTo) {
1263
+ if (this.groupSortOrder === dir)
1264
+ return;
1265
+ this.groupSortOrder = dir;
1266
+ this._groupPages.clear();
1267
+ this.onNeedRender();
1268
+ const openGroup = this.getSingleOpenGroup();
1269
+ if (openGroup)
1270
+ scrollTo(openGroup);
1271
+ }
1272
+ }
1273
+
1274
+ const LAZY_FIELD_LOADING_VALUE = '__lazy_loading__';
1275
+ const LAZY_FIELD_ERROR_VALUE = '__lazy_error__';
1276
+ class LazyFieldManager {
1277
+ constructor() {
1278
+ this._status = new Map();
1279
+ this._data = new Map();
1280
+ this._cancels = new Map();
1281
+ }
1282
+ get status() {
1283
+ return this._status;
1284
+ }
1285
+ get data() {
1286
+ return this._data;
1287
+ }
1288
+ getStatus(key) {
1289
+ return this._status.get(key) ?? 'idle';
1290
+ }
1291
+ getRenderType(rowValue) {
1292
+ if (rowValue === LAZY_FIELD_LOADING_VALUE)
1293
+ return 'loading';
1294
+ if (rowValue === LAZY_FIELD_ERROR_VALUE)
1295
+ return 'error';
1296
+ return 'value';
1297
+ }
1298
+ cancelAll() {
1299
+ this._cancels.forEach(s => { s.next(); s.complete(); });
1300
+ this._cancels.clear();
1301
+ this._status = new Map();
1302
+ this._data = new Map();
1303
+ }
1304
+ retry(field, resolvedData, callbacks) {
1305
+ const k = field.key;
1306
+ const prev = this._cancels.get(k);
1307
+ if (prev) {
1308
+ prev.next();
1309
+ prev.complete();
1310
+ }
1311
+ this._cancels.delete(k);
1312
+ const newStatus = new Map(this._status);
1313
+ newStatus.delete(k);
1314
+ this._status = newStatus;
1315
+ const newData = new Map(this._data);
1316
+ newData.delete(k);
1317
+ this._data = newData;
1318
+ this.fetch(field, resolvedData, callbacks);
1319
+ }
1320
+ fetch(field, resolvedData, callbacks) {
1321
+ if (!field.lazy)
1322
+ return;
1323
+ const k = field.key;
1324
+ if (this._status.get(k) === 'loading')
1325
+ return;
1326
+ const prev = this._cancels.get(k);
1327
+ if (prev) {
1328
+ prev.next();
1329
+ prev.complete();
1330
+ }
1331
+ const cancel$ = new Subject();
1332
+ this._cancels.set(k, cancel$);
1333
+ const newStatus = new Map(this._status);
1334
+ newStatus.set(k, 'loading');
1335
+ this._status = newStatus;
1336
+ callbacks.onRefresh();
1337
+ field.lazy.fetchFn().pipe(take(1), takeUntil$1(cancel$)).subscribe({
1338
+ next: (values) => {
1339
+ const rowMap = new Map();
1340
+ values.forEach((value, i) => {
1341
+ if (i < resolvedData.length)
1342
+ rowMap.set(resolvedData[i], value);
1343
+ });
1344
+ const newData = new Map(this._data);
1345
+ newData.set(k, rowMap);
1346
+ this._data = newData;
1347
+ const newStatus2 = new Map(this._status);
1348
+ newStatus2.set(k, 'loaded');
1349
+ this._status = newStatus2;
1350
+ callbacks.onRefresh();
1351
+ callbacks.onMarkForCheck();
1352
+ },
1353
+ error: () => {
1354
+ const newStatusErr = new Map(this._status);
1355
+ newStatusErr.set(k, 'error');
1356
+ this._status = newStatusErr;
1357
+ callbacks.onRefresh();
1358
+ callbacks.onMarkForCheck();
1359
+ },
1360
+ });
1361
+ }
1362
+ }
1363
+
1364
+ /**
1365
+ * Generated bundle index. Do not edit.
1366
+ */
1367
+
1368
+ export { DEFAULT_MENU_ICONS, DEFAULT_MENU_LABELS, GroupByManager, LAZY_FIELD_ERROR_VALUE, LAZY_FIELD_LOADING_VALUE, LazyFieldManager, OrganizerButtonComponent, OrganizerButtonModule, OrganizerFacade, SlicePanelComponent, applyOrganizerEventToChart, buildYFields, chartConfigToOrganizer, chartConfigToState, chartConfigToUnifiedState, humanizeKey, resolveYKey, resolveYUnit };
1369
+ //# sourceMappingURL=oneteme-jquery-organizer.mjs.map