@elasticias/screens 0.0.16 → 1.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.
@@ -1,11 +1,12 @@
1
- import * as i0 from '@angular/core';
1
+ import * as _angular_core from '@angular/core';
2
2
  import { OnInit, OnDestroy, Signal, InjectionToken, AfterViewInit, ChangeDetectorRef, Injector } from '@angular/core';
3
- import { Subject } from 'rxjs';
4
- import { PermissionsEnum } from '@elasticias/types';
3
+ import { Subject, Observable } from 'rxjs';
4
+ import { Permissions } from '@elasticias/types';
5
5
  import { Router, ActivatedRoute } from '@angular/router';
6
6
  import { NgForm } from '@angular/forms';
7
7
  import { CacheService, ToastService, ConfirmDialogService } from '@elasticias/core';
8
8
  import { Table, TableLazyLoadEvent } from 'primeng/table';
9
+ import { Location } from '@angular/common';
9
10
 
10
11
  declare abstract class AbstractEntity {
11
12
  [key: string]: unknown;
@@ -21,8 +22,8 @@ declare abstract class AbstractComponent implements OnInit, OnDestroy {
21
22
  protected destroyed$: Subject<void>;
22
23
  abstract ngOnInit(): void;
23
24
  abstract ngOnDestroy(): void;
24
- static ɵfac: i0.ɵɵFactoryDeclaration<AbstractComponent, never>;
25
- static ɵcmp: i0.ɵɵComponentDeclaration<AbstractComponent, "ng-component", never, { "readOnly": { "alias": "readOnly"; "required": false; }; }, {}, never, never, true, never>;
25
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<AbstractComponent, never>;
26
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<AbstractComponent, "ng-component", never, { "readOnly": { "alias": "readOnly"; "required": false; }; }, {}, never, never, true, never>;
26
27
  }
27
28
 
28
29
  /**
@@ -50,7 +51,15 @@ declare class ScreenContext extends AbstractEntity {
50
51
  has: (key: string) => boolean;
51
52
  };
52
53
  getLabel(key: string): string;
53
- isGranted(permission: PermissionsEnum): boolean;
54
+ isGranted(permission: Permissions): boolean;
55
+ get hasReadPermission(): boolean;
56
+ get hasCreatePermission(): boolean;
57
+ get hasEditPermission(): boolean;
58
+ get hasDeletePermission(): boolean;
59
+ get hasDuplicatePermission(): boolean;
60
+ get hasPrintPermission(): boolean;
61
+ get hasExportPermission(): boolean;
62
+ get hasImportPermission(): boolean;
54
63
  isReadOnly(): boolean;
55
64
  }
56
65
 
@@ -82,6 +91,36 @@ declare class SearchEntity extends AbstractEntity {
82
91
  getSearchCriteria(): Record<string, unknown>;
83
92
  }
84
93
 
94
+ /**
95
+ * Built-in date-preset keys. `AbstractSearchScreenV2.buildDefaultDateRange`
96
+ * and `ef-datepicker-advanced` recognise these and compute start/end
97
+ * automatically; consumers can also pass a `string` for custom keys
98
+ * paired with a compute callback in the picker's preset list.
99
+ */
100
+ type EfDatePresetKey = 'today' | 'this_week' | 'this_month' | 'last_30_days' | 'last_90_days' | 'this_quarter' | 'this_year' | 'custom' | string;
101
+ /**
102
+ * Active date-range filter, owned by `AbstractSearchScreenV2.dateRange`
103
+ * and round-tripped through `ef-datepicker-advanced`. `start`/`end` are
104
+ * inclusive day boundaries (both at 00:00 local time).
105
+ */
106
+ interface EfDateRange {
107
+ /** Inclusive start. */
108
+ start: Date;
109
+ /** Inclusive end — 00:00 local on the last day. Strict comparisons
110
+ * should treat this as `< end + 1 day`. */
111
+ end: Date;
112
+ /** Which preset is active (`'custom'` for a manually picked range). */
113
+ presetKey: EfDatePresetKey;
114
+ /** Resolved literal label — formatted date range for `'custom'`,
115
+ * the preset's `label` (or built-in hint) for built-ins. Used by
116
+ * the trigger as the fallback when `labelKey` is empty. */
117
+ label: string;
118
+ /** Translation key for the trigger's bold value text. The picker
119
+ * renders this through `| translate` so it stays reactive when
120
+ * translations finish loading or the language changes. */
121
+ labelKey?: string;
122
+ }
123
+
85
124
  interface DefaultSort {
86
125
  field: string;
87
126
  direction: SortDirectionEnum;
@@ -103,6 +142,24 @@ declare abstract class ScreenConfig {
103
142
  REFRESH_ON_SAVE?: boolean;
104
143
  REF_DATA_OPTIONS?: LoadOptions;
105
144
  DEFAULT_SORT?: DefaultSort;
145
+ /**
146
+ * Backend entity type for the standardized change-history box. When set,
147
+ * AbstractDetailScreenV2 auto-loads the record's audit trail (via the
148
+ * AUDIT_HISTORY_SERVICE token) into its `auditEntries` signal — the screen
149
+ * only needs `<ef-change-history [entries]="auditEntries()" />`. Leave unset
150
+ * to opt out. Must match the backend IAuditable.AuditEntityType (e.g. 'Client').
151
+ */
152
+ AUDIT_ENTITY_TYPE?: string;
153
+ /**
154
+ * Report screens (AbstractReportScreenV2): starting period preset for the
155
+ * screen's ef-datepicker-advanced. Unset → 'last_30_days'.
156
+ */
157
+ DEFAULT_PERIOD?: EfDatePresetKey;
158
+ /**
159
+ * Report screens: translatable static lists preloaded before the first
160
+ * loadAll() — same mechanism as SEARCH_STATIC_LISTS on list screens.
161
+ */
162
+ REPORT_STATIC_LISTS?: string[];
106
163
  }
107
164
 
108
165
  /**
@@ -151,7 +208,7 @@ declare abstract class AbstractScreenComponent extends AbstractComponent impleme
151
208
  context: ScreenContext;
152
209
  connectedUser: any;
153
210
  entityForm: NgForm;
154
- serverErrors: i0.WritableSignal<{
211
+ serverErrors: _angular_core.WritableSignal<{
155
212
  [key: string]: string[];
156
213
  }>;
157
214
  protected refDataLoaded$: Subject<void>;
@@ -171,6 +228,20 @@ declare abstract class AbstractScreenComponent extends AbstractComponent impleme
171
228
  processGrants(): void;
172
229
  isFormValid(): boolean;
173
230
  getConfig(): any;
231
+ /**
232
+ * Read a query param from the activated route's snapshot. Shared
233
+ * accessor for deep-links on both search and detail screens (e.g.
234
+ * `?status=PendingApproval`, `?mode=duplicate`) so screens don't
235
+ * reach into `route.snapshot.queryParamMap` themselves.
236
+ */
237
+ protected queryParam(name: string): string | null;
238
+ /**
239
+ * Grant check for ANY screen (not just this one's `SCREEN` code) —
240
+ * e.g. an operational strip or report widget calling a
241
+ * differently-gated endpoint. For this screen's own grants prefer
242
+ * `context.isGranted(...)`.
243
+ */
244
+ protected hasGrant(screen: string, permission: Permissions): boolean;
174
245
  getBundleName(): string;
175
246
  setFormErrors(errors: {
176
247
  [key: string]: string[];
@@ -190,15 +261,16 @@ declare abstract class AbstractScreenComponent extends AbstractComponent impleme
190
261
  }): void;
191
262
  clearServerErrors(): void;
192
263
  disableAllControls(): void;
193
- static ɵfac: i0.ɵɵFactoryDeclaration<AbstractScreenComponent, never>;
194
- static ɵcmp: i0.ɵɵComponentDeclaration<AbstractScreenComponent, "ng-component", never, { "context": { "alias": "context"; "required": false; }; }, {}, never, never, true, never>;
264
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<AbstractScreenComponent, never>;
265
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<AbstractScreenComponent, "ng-component", never, { "context": { "alias": "context"; "required": false; }; }, {}, never, never, true, never>;
195
266
  }
196
267
 
197
268
  declare enum ScreenStateEnum {
198
269
  READ_ONLY = "READ_ONLY",
199
270
  DETAIL = "DETAIL",
200
271
  SEARCH = "SEARCH",
201
- CUSTOM = "CUSTOM"
272
+ CUSTOM = "CUSTOM",
273
+ REPORT = "REPORT"
202
274
  }
203
275
  declare enum StateUtilsEnum {
204
276
  REMOVE = "removeState",
@@ -235,8 +307,8 @@ declare abstract class AbstractDetailScreenComponent extends AbstractScreenCompo
235
307
  print(): void;
236
308
  delete(): void;
237
309
  navigateBack(): void;
238
- static ɵfac: i0.ɵɵFactoryDeclaration<AbstractDetailScreenComponent, never>;
239
- static ɵcmp: i0.ɵɵComponentDeclaration<AbstractDetailScreenComponent, "ng-component", never, {}, {}, never, never, true, never>;
310
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<AbstractDetailScreenComponent, never>;
311
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<AbstractDetailScreenComponent, "ng-component", never, {}, {}, never, never, true, never>;
240
312
  }
241
313
 
242
314
  /**
@@ -279,21 +351,763 @@ declare abstract class AbstractSearchScreenComponent extends AbstractScreenCompo
279
351
  duplicate(id: any): void;
280
352
  clear(): void;
281
353
  ngOnDestroy(): void;
282
- static ɵfac: i0.ɵɵFactoryDeclaration<AbstractSearchScreenComponent, never>;
283
- static ɵcmp: i0.ɵɵComponentDeclaration<AbstractSearchScreenComponent, "ng-component", never, {}, {}, never, never, true, never>;
354
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<AbstractSearchScreenComponent, never>;
355
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<AbstractSearchScreenComponent, "ng-component", never, {}, {}, never, never, true, never>;
356
+ }
357
+
358
+ /**
359
+ * Display shape for an active filter chip in the smart-bar. The
360
+ * abstract search screen owns a writable signal of `ActiveFilter[]`
361
+ * that consumers populate when an advanced filter is applied;
362
+ * `removeFilter(key)` slices the matching entry out.
363
+ */
364
+ interface ActiveFilter {
365
+ /** Stable identifier — passed back to `removeFilter`. */
366
+ key: string;
367
+ /** i18n key for the chip's label (e.g. `'filter_client'`). */
368
+ labelKey: string;
369
+ /** Pre-formatted display value (e.g. `'Leila Bennani'`,
370
+ * `'30 derniers jours'`). The chip renders this verbatim. */
371
+ value: string;
372
+ }
373
+
374
+ /**
375
+ * Built-in cell-rendering types. Each type ships a default cell
376
+ * template in ef-data-card; the `'custom'` type defers to a
377
+ * `<ng-template efColumnTemplate="…">` projected by the consumer.
378
+ *
379
+ * Lives in `@elasticias/screens` so AbstractSearchScreenV2 can return
380
+ * column descriptors from its `add*Column` builders without a
381
+ * circular import on `@elasticias/ui`. ef-data-card consumes the same
382
+ * type via re-export.
383
+ */
384
+ type EfDataCardColumnType = 'text' | 'number' | 'money' | 'date' | 'datetime' | 'boolean' | 'mono' | 'chip' | 'status' | 'reference' | 'custom';
385
+ type EfDataCardColumnAlign = 'start' | 'center' | 'end';
386
+ type EfDataCardSortDirection = 'asc' | 'desc';
387
+ interface EfDataCardSort {
388
+ /** Backend field name (matches `column.sortField` or `column.field`). */
389
+ field: string;
390
+ direction: EfDataCardSortDirection;
391
+ }
392
+ interface EfDataCardColumn {
393
+ /**
394
+ * Stable column identifier. Used as the trackBy key, the
395
+ * `efColumnTemplate` selector, and the cell's `data-col`
396
+ * attribute. Must be unique within a table.
397
+ */
398
+ id: string;
399
+ /** Dotted path into the row (defaults to `id`). */
400
+ field?: string;
401
+ /** Direct header text — used only when `headerKey` is empty. */
402
+ header?: string;
403
+ /** Translation key for the header — preferred. */
404
+ headerKey?: string;
405
+ /** Built-in renderer; falls back to `'text'`. */
406
+ type?: EfDataCardColumnType;
407
+ /** `'end'` is auto-applied for `'number'` / `'money'`. */
408
+ align?: EfDataCardColumnAlign;
409
+ /** Inline CSS width (e.g. `'40px'`, `'15%'`). */
410
+ width?: string;
411
+ /** Extra CSS classes to apply on the `<td>` (e.g. `'num'`). */
412
+ cellClass?: string;
413
+ /** Sort enabled for this column (default: `false` — opt-in). */
414
+ sortable?: boolean;
415
+ /** Backend sort field — defaults to `field` then `id`. */
416
+ sortField?: string;
417
+ /** Date / datetime: pipe format string (default: `'shortDate'` / `'short'`). */
418
+ dateFormat?: string;
419
+ /** Money: ISO currency code (default: `'EUR'`). */
420
+ currencyCode?: string;
421
+ /** Money: how the symbol renders (default: `'symbol'`). */
422
+ currencyDisplay?: 'symbol' | 'code' | 'name';
423
+ /** Locale for number / date / currency pipes (default: `'fr-FR'`). */
424
+ locale?: string;
425
+ /** Number/money fraction digits. */
426
+ minFractionDigits?: number;
427
+ maxFractionDigits?: number;
428
+ /** Reference data lookup key for `type: 'reference'`. */
429
+ referenceKey?: string;
430
+ referenceValueField?: string;
431
+ referenceLabelField?: string;
432
+ /** Chip column: prefix prepended to the value to form the class
433
+ * (e.g. `'chip-'` so value `'pending'` → `'chip chip-pending'`). */
434
+ chipPrefix?: string;
435
+ }
436
+ /**
437
+ * Caller-friendly opts for `addReferenceColumn` — exposes
438
+ * `valueField` / `labelField` shorthands instead of the verbose
439
+ * `referenceValueField` / `referenceLabelField` properties.
440
+ */
441
+ interface EfReferenceColumnOpts extends Partial<EfDataCardColumn> {
442
+ /** Shorthand for `referenceValueField` (defaults to `'id'`). */
443
+ valueField?: string;
444
+ /** Shorthand for `referenceLabelField` (defaults to `'label'`). */
445
+ labelField?: string;
446
+ }
447
+
448
+ /**
449
+ * Declarative definition of an advanced-search select filter rendered in
450
+ * the filter drawer. One `<ef-select>` is rendered per entry; the screen
451
+ * stays type-agnostic — `key` is simply the criteria property name sent to
452
+ * the backend (which must expose a matching typed query prop, e.g.
453
+ * `CityIds: List<int>`, `ClientIds: List<string>`, a scalar code, …). Works
454
+ * for both single-select (`multiple` omitted/false → scalar value) and
455
+ * multi-select (`multiple: true` → array value).
456
+ */
457
+ interface AdvancedSelectFilter {
458
+ /** Criteria property name sent to the backend (must match a typed query prop). */
459
+ key: string;
460
+ /** Reference-data key supplying the options (e.g. `'cities'`). */
461
+ refKey: string;
462
+ /** i18n key for the field label (rendered by `ef-select`). */
463
+ labelKey: string;
464
+ /** Multi-select when `true` (array value); single-select otherwise (scalar). */
465
+ multiple?: boolean;
466
+ /** Option value field (default `'code'`). */
467
+ valueField?: string;
468
+ /** Option label field (default `'label'`). */
469
+ labelField?: string;
470
+ /** Placeholder i18n key (default `'common_all'`). */
471
+ placeholderKey?: string;
472
+ }
473
+ /**
474
+ * Signal-first counterpart to {@link AbstractSearchScreenComponent}.
475
+ *
476
+ * Built for V2 + zoneless apps: state is exposed as signals
477
+ * (`items`, `loading`, `errorMsg`, `totalCount`, `criteria`) and the
478
+ * class is intentionally **decoupled from PrimeNG `<p-table>`** —
479
+ * subclasses render however they want (Comptoir `.tbl` patterns,
480
+ * cards, virtual lists, …) and call the helpers below to mutate the
481
+ * search criteria + re-execute.
482
+ *
483
+ * Same `ScreenConfig` surface as the legacy abstract:
484
+ * - `SCREEN` (string code matching backend grants)
485
+ * - `SERVICE` (NSwag client class with `search(criteria)` method)
486
+ * - `SEARCH_REFERENTIALS_KEYS` / `SEARCH_STATIC_LISTS` for ref-data
487
+ * - `DEFAULT_SORT` for first load
488
+ *
489
+ * Subclasses typically:
490
+ *
491
+ * ```ts
492
+ * @Component({ ... })
493
+ * export class FooComponent extends AbstractSearchScreenV2<FooDto> {
494
+ * protected override getConfig() { return FooConfig; }
495
+ * readonly rows = computed(() => this.items().map(toRow));
496
+ * }
497
+ * ```
498
+ */
499
+ declare abstract class AbstractSearchScreenV2<TItem = any> extends AbstractScreenComponent implements OnInit {
500
+ protected readonly screenState = ScreenStateEnum.SEARCH;
501
+ protected readonly injector: Injector;
502
+ private serviceInstance;
503
+ /** Monotonic guard: bumped on every `search()` call so an
504
+ * out-of-order (stale) response from an earlier search can be
505
+ * dropped instead of overwriting the latest results. */
506
+ private searchSeq;
507
+ /** Current search results — populated after each `search()`. */
508
+ readonly items: _angular_core.WritableSignal<TItem[]>;
509
+ readonly totalCount: _angular_core.WritableSignal<number>;
510
+ readonly loading: _angular_core.WritableSignal<boolean>;
511
+ readonly errorMsg: _angular_core.WritableSignal<string | null>;
512
+ /** Live search criteria (paging, sort, text, dates, custom). */
513
+ readonly criteria: _angular_core.WritableSignal<SearchEntity>;
514
+ /**
515
+ * Active date-range filter shown in `ef-datepicker-advanced`'s
516
+ * trigger. Display-only at construction time — actually filters
517
+ * the search once `onDateRangeChange()` fires (or a screen wires
518
+ * one in `ngOnInit`).
519
+ *
520
+ * Override `buildDefaultDateRange()` per screen to ship a
521
+ * different starting preset.
522
+ */
523
+ readonly dateRange: _angular_core.WritableSignal<EfDateRange>;
524
+ /** Free-text search input value — bound `[(searchText)]` on
525
+ * ef-smart-bar; drives `runSearch()`. */
526
+ readonly searchQuery: _angular_core.WritableSignal<string>;
527
+ /** Active status pill-group selection (defaults to `'all'`). */
528
+ readonly statusFilter: _angular_core.WritableSignal<string>;
529
+ /** Whether the advanced-filter drawer is open. */
530
+ readonly drawerOpen: _angular_core.WritableSignal<boolean>;
531
+ /** Active named filter chips shown in the smart-bar. */
532
+ readonly activeFilters: _angular_core.WritableSignal<ActiveFilter[]>;
533
+ /** Advanced-filter select definitions. Empty = no advanced filters. */
534
+ readonly advancedFilters: AdvancedSelectFilter[];
535
+ /** Criteria key of the screen's activation tri-state filter
536
+ * (`ef-activation-filter`), e.g. `'isActive'`. When set, the base
537
+ * applies, baseline-clears, and cache-restores the boolean like any
538
+ * declared advanced filter — the screen only binds the component to
539
+ * `advancedValues()` / `setAdvancedValue()`. `null` = no activation
540
+ * filter. */
541
+ protected readonly activationFilterKey: string | null;
542
+ /** Live values per advanced filter, keyed by `AdvancedSelectFilter.key`. */
543
+ readonly advancedValues: _angular_core.WritableSignal<Record<string, unknown>>;
544
+ /** Store the picked value(s) for one advanced filter (no search yet —
545
+ * the drawer's `(apply)` runs it). */
546
+ setAdvancedValue(key: string, value: unknown): void;
547
+ /** Push every advanced-filter value into the criteria as typed
548
+ * top-level props and re-run the search. Wired to the drawer's
549
+ * `(apply)`. Every DECLARED key is written on apply — a cleared
550
+ * control must actively remove its (possibly cache-restored)
551
+ * criteria value, not silently leave it behind. */
552
+ applyAdvancedFilters(): void;
553
+ /** Selected row ids — keyed by `String(rowId(row))`. */
554
+ readonly selected: _angular_core.WritableSignal<ReadonlySet<string>>;
555
+ /** Live count derived from `selected`. */
556
+ readonly selectionCount: _angular_core.Signal<number>;
557
+ readonly currentSort: _angular_core.Signal<EfDataCardSort | null>;
558
+ readonly showViewAction: _angular_core.WritableSignal<boolean>;
559
+ readonly showEditAction: _angular_core.WritableSignal<boolean>;
560
+ readonly showDuplicateAction: _angular_core.WritableSignal<boolean>;
561
+ readonly showDeleteAction: _angular_core.WritableSignal<boolean>;
562
+ /**
563
+ * PK accessor for a row. Default returns `row?.id` — override when
564
+ * the backend names its primary key something else (e.g. sales-orders'
565
+ * `orderId`). Used by the row-actions dispatcher and as the implicit
566
+ * navigateToDetails / edit / delete argument.
567
+ */
568
+ rowId(row: any): any;
569
+ /**
570
+ * Dispatch helper wired to ef-data-card's `(rowAction)` output and
571
+ * the auto-rendered row-actions cell. Routes the standard four
572
+ * actions to the inherited methods so subclasses don't have to
573
+ * declare per-screen rowActions arrays.
574
+ */
575
+ onRowAction(action: 'view' | 'edit' | 'duplicate' | 'delete', row: any): void;
576
+ /**
577
+ * Wired to `<ef-datepicker-advanced (rangeChange)>` — stores the
578
+ * range for trigger display and pushes it into the search criteria
579
+ * so the next `search()` filters by it.
580
+ */
581
+ onDateRangeChange(range: EfDateRange): void;
582
+ /**
583
+ * Reset the date range to the default. Called automatically by
584
+ * `clear()` so screens don't have to remember to invoke it from
585
+ * their own `clearAll()` orchestration.
586
+ */
587
+ protected resetDateRange(): void;
588
+ /**
589
+ * Build the default `EfDateRange`. Override per screen to ship a
590
+ * different default — e.g., a 90-day window for low-velocity
591
+ * catalogues. Default: last 30 days.
592
+ */
593
+ protected buildDefaultDateRange(): EfDateRange;
594
+ /** Today at 00:00 local time. */
595
+ protected startOfToday(): Date;
596
+ /** N days before today, at 00:00 local time. */
597
+ protected daysAgo(n: number): Date;
598
+ /** Wired to filter-drawer's `(apply)` — pushes the current
599
+ * searchQuery into the criteria and re-runs the search. */
600
+ runSearch(): void;
601
+ /** Pill-group click handler. Stores the selected status code; the
602
+ * search itself only re-runs once the consumer pushes the value
603
+ * into the criteria (or wires it via beforeSearch). */
604
+ setStatus(key: string): void;
605
+ toggleDrawer(): void;
606
+ /** Drop a chip from the active-filters list. */
607
+ removeFilter(key: string): void;
608
+ toggleSelection(id: string): void;
609
+ /** Toggle every visible row in or out of the selection in one shot. */
610
+ toggleAllSelection(): void;
611
+ isSelected(id: string): boolean;
612
+ /** Selection checkbox column — paired with `efColumnTemplate="select"`
613
+ * for the row's checkbox. 40px wide, no other props. */
614
+ protected addSelectColumn(width?: string): EfDataCardColumn;
615
+ /** Plain text column. */
616
+ protected addTextColumn(field: string, headerKey: string, opts?: Partial<EfDataCardColumn>): EfDataCardColumn;
617
+ /** Monospace column — JetBrains Mono with tabular-nums; for codes,
618
+ * IDs, refs, anything where character alignment matters. */
619
+ protected addMonoColumn(field: string, headerKey: string, opts?: Partial<EfDataCardColumn>): EfDataCardColumn;
620
+ /** Number column — end-aligned, integer by default. Override
621
+ * `minFractionDigits` / `maxFractionDigits` via opts for decimals. */
622
+ protected addNumberColumn(field: string, headerKey: string, opts?: Partial<EfDataCardColumn>): EfDataCardColumn;
623
+ /** Money column — end-aligned. ISO currency code defaults to `'MAD'`
624
+ * (Elasticias' primary tenant locale); pass `opts.currencyCode` for
625
+ * euros / USD / etc. */
626
+ protected addMoneyColumn(field: string, headerKey: string, opts?: Partial<EfDataCardColumn>): EfDataCardColumn;
627
+ /** Date column — formats as `dd/MM/yyyy` by default. */
628
+ protected addDateColumn(field: string, headerKey: string, opts?: Partial<EfDataCardColumn>): EfDataCardColumn;
629
+ /** Datetime column — formats as `dd/MM/yyyy HH:mm` by default. */
630
+ protected addDatetimeColumn(field: string, headerKey: string, opts?: Partial<EfDataCardColumn>): EfDataCardColumn;
631
+ /** Boolean column — renders the `bool yes / bool no` indicator. */
632
+ protected addBooleanColumn(field: string, headerKey: string, opts?: Partial<EfDataCardColumn>): EfDataCardColumn;
633
+ /** Static-class chip column — renders `chip <chipPrefix><value>`.
634
+ * Use `addStatusColumn` for reference_data-driven palettes. */
635
+ protected addChipColumn(field: string, headerKey: string, opts?: Partial<EfDataCardColumn>): EfDataCardColumn;
636
+ /** Status chip column — palette + label resolved from
637
+ * `reference_data` via `referenceKey`. Sortable by default. */
638
+ protected addStatusColumn(field: string, headerKey: string, referenceKey: string, opts?: Partial<EfDataCardColumn>): EfDataCardColumn;
639
+ /** Reference column — looks up `field`'s value in the reference
640
+ * list keyed by `referenceKey`, renders the matching item's label.
641
+ * Defaults: `valueField: 'id'`, `labelField: 'label'`. */
642
+ protected addReferenceColumn(field: string, headerKey: string, referenceKey: string, opts?: EfReferenceColumnOpts): EfDataCardColumn;
643
+ ngOnInit(): void;
644
+ /**
645
+ * Deep-link support: `/<list>?status=<code>` pre-selects the status
646
+ * pill after the initial criteria bootstrap (so the reset doesn't
647
+ * clobber it). Runs through `setStatus()`, so a subclass override
648
+ * that pushes the status into the criteria (the usual pattern) gets
649
+ * the deep-linked value too. No-op without the query param.
650
+ */
651
+ private applyDeepLinkStatus;
652
+ /** Restore criteria from cache (returning to a screen) or seed defaults. */
653
+ private bootstrapInitialSearch;
654
+ /**
655
+ * Cache-restore sync: when a revisit restores cached criteria,
656
+ * reflect the restored filters back into the filter-bar UI state so
657
+ * what the drawer / search box displays matches what the search will
658
+ * actually send (otherwise a stale criteria filter keeps applying
659
+ * while every control reads "Tous"). Base handles the free-text
660
+ * input and the declared advanced filters; override (calling super)
661
+ * to sync screen-specific state — status pill, custom switches, ….
662
+ */
663
+ protected restoreFilterUiFromCriteria(cached: Record<string, any>): void;
664
+ /**
665
+ * Run a search with the current `criteria()`. Always populates
666
+ * `items` / `totalCount` / `loading` / `errorMsg` and persists the
667
+ * criteria to cache on success so revisits can restore.
668
+ */
669
+ search(): void;
670
+ setSearchText(text: string): void;
671
+ setPage(pageNumber: number, pageSize?: number): void;
672
+ setSort(field: string, direction?: string): void;
673
+ setDateRange(start: Date | null, end: Date | null): void;
674
+ /** Patch arbitrary extra fields onto the criteria (for module-specific filters). */
675
+ patchCriteria(patch: Record<string, any>): void;
676
+ /** Reset everything to defaults and re-fetch. Subclasses bind this
677
+ * to ef-smart-bar's `(clear)` output directly — no per-screen
678
+ * `clearAll()` orchestration needed. */
679
+ clear(): void;
680
+ private cloneCriteria;
681
+ /** Resolve the list-screen URL at call time. Drops query / fragment
682
+ * and strips a trailing slash. */
683
+ protected resolveListUrl(): string;
684
+ navigateToDetails(id?: any): void;
685
+ /** Open the create form. */
686
+ add(): void;
687
+ edit(id: any): void;
688
+ delete(id: any): void;
689
+ duplicate(id: any): void;
690
+ ngOnDestroy(): void;
691
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<AbstractSearchScreenV2<any>, never>;
692
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<AbstractSearchScreenV2<any>, "ng-component", never, {}, {}, never, never, true, never>;
693
+ }
694
+
695
+ /**
696
+ * Declarative custom action consumed by `ef-detail-toolbar`'s
697
+ * `[customActions]` input AND produced by
698
+ * `AbstractDetailScreenV2.getCustomActions()`.
699
+ *
700
+ * Lives in `@elasticias/screens` so the abstract can return arrays
701
+ * of these without a circular dep on `@elasticias/ui`. ef-detail-
702
+ * toolbar consumes the same type via re-export from
703
+ * `@elasticias/ui/.../ef-detail-toolbar.types`.
704
+ *
705
+ * Custom actions render in the toolbar's right group, before the
706
+ * standard `print | duplicate | delete | save` row, separated by
707
+ * a `|` divider when both groups are present.
708
+ */
709
+ interface EfDetailToolbarAction {
710
+ /** Stable identifier for trackBy / tests. */
711
+ id?: string;
712
+ /** Translation key — preferred. */
713
+ labelKey?: string;
714
+ /** Direct label fallback when `labelKey` is empty. */
715
+ label?: string;
716
+ /** PrimeIcons class (e.g. `'pi pi-eye'`). */
717
+ icon?: string;
718
+ /**
719
+ * Visual tone:
720
+ * - `'ghost'` (default) — outlined paper-alt button
721
+ * - `'primary'` — ink-active fill
722
+ * - `'tenant'` — tenant accent fill (use for the
723
+ * screen's principal action)
724
+ * - `'danger'` — ghost styling tinted with
725
+ * `--st-cancelled-fg`
726
+ */
727
+ severity?: 'ghost' | 'primary' | 'tenant' | 'danger';
728
+ /**
729
+ * Hide the action regardless of the permission check. Useful for
730
+ * conditional visibility tied to entity state (e.g. only show
731
+ * "Approve" when `entity.status === 'pending'`). Default `true`.
732
+ */
733
+ visible?: boolean;
734
+ /** Disable without removing. */
735
+ disabled?: boolean;
736
+ /**
737
+ * If set, the action is hidden unless the bound `ScreenContext`
738
+ * grants this permission. Actions with no `permission` always
739
+ * show (subject to `visible`).
740
+ */
741
+ permission?: Permissions;
742
+ /** Click handler. */
743
+ command?: () => void;
744
+ }
745
+
746
+ /**
747
+ * Signal-first counterpart to {@link AbstractDetailScreenComponent}.
748
+ *
749
+ * Same business mechanism as v1 but signal-based and zoneless-friendly:
750
+ * `entity` / `entityId` / `editionState` / `duplicateMode` / `loading`
751
+ * / `errorMsg` are all writable signals.
752
+ *
753
+ * Routing convention (V2):
754
+ * - `/<resource>/details` → new (create mode, `editionState=false`)
755
+ * - `/<resource>/details/:id` → edit mode (loads via `service.get(id)`)
756
+ * - `/<resource>/details/:id?mode=duplicate` → loads then strips id on save
757
+ *
758
+ * The same component handles all three paths — the route param +
759
+ * `mode` query string drives the state. After a successful create
760
+ * (or duplicate-save), the screen navigates to
761
+ * `/<resource>/details/<new-id>` so the user lands in edit mode.
762
+ *
763
+ * Subclasses typically override:
764
+ * - `getConfig()` — returns ScreenConfig with SERVICE
765
+ * - `beforeSave(): boolean` — return false to cancel save
766
+ * (default: true)
767
+ * - `afterLoad()` — post-process loaded entity
768
+ * - `afterSave(result)` — default invalidates refs + navigates
769
+ * to the new edit URL after create
770
+ * - `onSaveError(errors)` — surface form-level validation errors
771
+ * - `customizeDuplicatedEntity` — clean fields before duplicating
772
+ *
773
+ * ```ts
774
+ * @Component({ ... })
775
+ * export class FooDetailComponent extends AbstractDetailScreenV2<FooDto> {
776
+ * override getConfig() { return FooConfig; }
777
+ *
778
+ * protected override beforeSave(): boolean {
779
+ * // validate this.entity() shape; return false to cancel
780
+ * return true;
781
+ * }
782
+ * }
783
+ * ```
784
+ */
785
+ declare abstract class AbstractDetailScreenV2<TItem extends object = any> extends AbstractScreenComponent implements OnInit {
786
+ protected readonly screenState = ScreenStateEnum.DETAIL;
787
+ protected readonly injector: Injector;
788
+ protected readonly location: Location;
789
+ private readonly destroyRef;
790
+ private readonly auditHistoryService;
791
+ protected serviceInstance: any;
792
+ /**
793
+ * Change-history entries for the loaded record, newest first. Populated
794
+ * automatically when the config sets `AUDIT_ENTITY_TYPE` and an
795
+ * `AUDIT_HISTORY_SERVICE` is provided. Bind it directly:
796
+ * `<ef-change-history [entries]="auditEntries()" />`.
797
+ */
798
+ readonly auditEntries: _angular_core.WritableSignal<any[]>;
799
+ /** Loaded entity. Empty object when on `/details` (create mode). */
800
+ readonly entity: _angular_core.WritableSignal<TItem>;
801
+ /** PK from the route param, or `null` on `/details` (new). */
802
+ readonly entityId: _angular_core.WritableSignal<any>;
803
+ /** Edit-mode flag — true when an id is present and we're not duplicating. */
804
+ readonly editionState: _angular_core.WritableSignal<boolean>;
805
+ /**
806
+ * When true, the loaded entity is treated as a template — saving
807
+ * runs `service.create()` instead of `service.update()`. Set by
808
+ * the `?mode=duplicate` query param.
809
+ */
810
+ readonly duplicateMode: _angular_core.WritableSignal<boolean>;
811
+ /** True while service.get / create / update / delete is in flight. */
812
+ readonly loading: _angular_core.WritableSignal<boolean>;
813
+ /** Last load / save error message — empty string when none. */
814
+ readonly errorMsg: _angular_core.WritableSignal<string | null>;
815
+ /**
816
+ * Reactive list of custom actions for `<ef-detail-toolbar>`'s
817
+ * `[customActions]` input — recomputes whenever editionState /
818
+ * duplicateMode flip. Default contents:
819
+ * - Edit mode → empty (the standard `print | duplicate |
820
+ * delete | save` row covers it)
821
+ * - Create mode → `[Cancel]` — navigates back to the list
822
+ * - Duplicate mode → `[Cancel]` — drops `?mode=duplicate` and
823
+ * returns to edit view of the source entity
824
+ *
825
+ * Subclasses override `getCustomActions()` to add screen-specific
826
+ * actions (Approve / Print PDF / Mark as paid / etc.). Call
827
+ * `super.getCustomActions()` to keep the Cancel default.
828
+ */
829
+ readonly customActions: _angular_core.Signal<readonly EfDetailToolbarAction[]>;
830
+ /**
831
+ * Builder for `customActions`. Override per screen to add or
832
+ * replace the defaults. Pure function — reads other signals
833
+ * freely, returns a fresh array each call.
834
+ */
835
+ protected getCustomActions(): EfDetailToolbarAction[];
836
+ ngOnInit(): void;
837
+ /**
838
+ * Merge a partial patch into the `entity` signal — the canonical
839
+ * way for form inputs to write back. Spread-immutably so OnPush
840
+ * change detection picks it up.
841
+ *
842
+ * ```html
843
+ * <ef-input-text
844
+ * variant="comptoir"
845
+ * [value]="title()"
846
+ * (valueChangeEvent)="patchEntity({ title: $event })"
847
+ * />
848
+ * ```
849
+ */
850
+ patchEntity(patch: Partial<TItem>): void;
851
+ /** Fetch the entity from the backend and populate `entity`. */
852
+ loadData(): void;
853
+ /**
854
+ * Persist the entity. Routes to `service.create()` when in new
855
+ * or duplicate mode, `service.update(id, entity)` when editing.
856
+ * Subclass `beforeSave()` can return `false` to cancel.
857
+ */
858
+ save(): void;
859
+ /**
860
+ * Soft-delete-then-navigate-back. Confirms with the user first.
861
+ */
862
+ delete(): void;
863
+ /** Navigate to `/details/:id?mode=duplicate` so the abstract can
864
+ * reload the source entity, treat it as a template, and persist
865
+ * via `service.create()` after the user hits Save.
866
+ * Without the id, the duplicate route would have nothing to
867
+ * fetch — `entity` would be empty and the clone-as-template
868
+ * flow would fall back to creating a blank record. */
869
+ duplicate(): void;
870
+ /**
871
+ * Cancel handler for the default toolbar action:
872
+ * - Duplicate mode → drop `?mode=duplicate` and return to
873
+ * `/details/:id`. The route subscription re-fires and reloads
874
+ * the source entity, discarding any in-memory edits.
875
+ * - Create mode (no id) → navigate back to the list.
876
+ *
877
+ * Subclasses may override to add a confirm dialog when the form
878
+ * is dirty.
879
+ */
880
+ cancel(): void;
881
+ /** Navigate back to the list (strip `/details` and any id). */
882
+ navigateBack(): void;
883
+ /** Subclass print hook — no-op default. */
884
+ print(): void;
885
+ /**
886
+ * Load the standardized change-history into `auditEntries` when the config
887
+ * opts in via `AUDIT_ENTITY_TYPE` and an `AUDIT_HISTORY_SERVICE` is provided.
888
+ * Called automatically after a successful `loadData()`. Failures degrade to
889
+ * an empty history rather than blocking the screen.
890
+ */
891
+ protected loadAuditHistory(): void;
892
+ /**
893
+ * Called right before `save()` dispatches to the backend. Return
894
+ * `false` to cancel (e.g., form validation failed). Default: no-op.
895
+ */
896
+ protected beforeSave(): boolean | void;
897
+ /** Override to post-process the loaded `entity` (or to refresh
898
+ * derived signals). Default: no-op. */
899
+ protected afterLoad(): void;
900
+ /**
901
+ * Default post-save behaviour:
902
+ * - invalidate / refresh reference data per ScreenConfig
903
+ * - after a CREATE (or duplicate-save), navigate to
904
+ * `/details/<new-id>` so the user lands in edit mode
905
+ */
906
+ protected afterSave(result: any): void;
907
+ /** Override to surface form-level validation errors after a 4xx
908
+ * response. Default: no-op (errors are already on `serverErrors`). */
909
+ protected onSaveError(_errors: {
910
+ [key: string]: string[];
911
+ }): void;
912
+ /** Strip id (if present) from the entity before a duplicate save. */
913
+ protected prepareEntityForDuplication(): any;
914
+ /** Override to clear additional fields (codes, slugs, refs)
915
+ * before saving a duplicated entity. */
916
+ protected customizeDuplicatedEntity(entity: any): any;
917
+ /** Resolve the bare `/<resource>/details` base URL — strips a
918
+ * trailing `:id`, query string, fragment, trailing slash. */
919
+ protected detailsBaseUrl(): string;
920
+ ngOnDestroy(): void;
921
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<AbstractDetailScreenV2<any>, never>;
922
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<AbstractDetailScreenV2<any>, "ng-component", never, {}, {}, never, never, true, never>;
923
+ }
924
+
925
+ type ReportWidgetStatus = 'loading' | 'ready' | 'error';
926
+ /**
927
+ * Handle returned by {@link AbstractReportScreenV2.widget}. Each widget owns
928
+ * an independent loading/error lifecycle — one failing report block never
929
+ * blanks its siblings.
930
+ */
931
+ interface ReportWidget<T> {
932
+ /** Last successful payload — null until the first `ready`. */
933
+ readonly data: Signal<T | null>;
934
+ readonly status: Signal<ReportWidgetStatus>;
935
+ /** Re-run this widget's loader against the current period. */
936
+ reload(): void;
937
+ }
938
+ /**
939
+ * Signal-first base for report/dashboard screens — the third V2 abstract,
940
+ * alongside {@link AbstractSearchScreenV2} / AbstractDetailScreenV2.
941
+ *
942
+ * Owns the report CHASSIS only: period state (`dateRange`, seeded from the
943
+ * config's `DEFAULT_PERIOD` preset), per-widget lifecycle (`widget()` +
944
+ * `loadAll()`), `REPORT_STATIC_LISTS` preload, and Export-gated CSV
945
+ * (Task 2). Widget COMPOSITION stays in each screen's template — a
946
+ * metadata-driven report renderer was rejected in ADR-015 and this class
947
+ * must not become its frontend half.
948
+ *
949
+ * `ScreenConfig` surface: `SCREEN` (backend screen code), `SERVICE` (NSwag
950
+ * reports client), `DEFAULT_PERIOD`, `REPORT_STATIC_LISTS`.
951
+ *
952
+ * Subclasses typically:
953
+ *
954
+ * ```ts
955
+ * export class FooDashboardComponent extends AbstractReportScreenV2 {
956
+ * protected override getConfig() { return FooDashboardConfig; }
957
+ * readonly kpisW = this.widget((start, end) =>
958
+ * this.client.kpis(new GetFooKpisQuery({ start, end })));
959
+ * }
960
+ * ```
961
+ *
962
+ * Loaders close over the screen's own filter signals; the screen calls
963
+ * `loadAll()` (period-wide) or `someW.reload()` (single widget) when its
964
+ * filters change. Period-independent blocks (operational strips) load
965
+ * outside the registry on purpose.
966
+ */
967
+ declare abstract class AbstractReportScreenV2 extends AbstractScreenComponent implements OnInit {
968
+ protected readonly screenState = ScreenStateEnum.REPORT;
969
+ protected readonly injector: Injector;
970
+ /** NSwag reports client resolved from `ScreenConfig.SERVICE` — expose a
971
+ * typed getter in the screen: `get client() { return this.reportsService as XClient; }` */
972
+ protected reportsService: any;
973
+ private readonly registeredWidgets;
974
+ /** Active period. Bound to `ef-datepicker-advanced`; every widget loader
975
+ * receives its UTC-normalized start/end. */
976
+ readonly dateRange: _angular_core.WritableSignal<EfDateRange>;
977
+ ngOnInit(): void;
978
+ /** Wired to `<ef-datepicker-advanced (rangeChange)>`. */
979
+ onDateRangeChange(range: EfDateRange): void;
980
+ /** Default period from the config's `DEFAULT_PERIOD` preset. */
981
+ protected buildDefaultDateRange(): EfDateRange;
982
+ /**
983
+ * Resolve a built-in {@link EfDatePresetKey} to an inclusive day range
984
+ * (both boundaries at 00:00 local). Unknown keys normalize to
985
+ * `last_30_days`. Weeks start Monday (matches `$dateTrunc`, ADR-015).
986
+ */
987
+ protected rangeFromPreset(key: EfDatePresetKey): EfDateRange;
988
+ private startOfToday;
989
+ /**
990
+ * Re-anchor a local-midnight Date to UTC midnight of the same calendar
991
+ * date. NSwag serializes via toISOString(); for UTC+ users (Morocco)
992
+ * local midnight would otherwise shift into the previous UTC day.
993
+ */
994
+ protected toUtcDate(d: Date): Date;
995
+ /**
996
+ * Register a report widget. The loader receives the current period's
997
+ * UTC-normalized start/end and returns the NSwag observable; extra
998
+ * filters are simply closed over from the screen's own signals.
999
+ */
1000
+ protected widget<T>(loader: (start: Date, end: Date) => Observable<T>): ReportWidget<T>;
1001
+ /** Reload every registered widget against the current period. */
1002
+ loadAll(): void;
1003
+ /** Export grant (ADR-011) on this screen's own code. Grants are loaded
1004
+ * once by `processGrants()` in ngOnInit — safe to call from templates. */
1005
+ canExport(): boolean;
1006
+ /** CSV download gated by the Export grant — silently no-ops without it. */
1007
+ protected exportCsv(filename: string, rows: object[], columns: {
1008
+ key: string;
1009
+ header: string;
1010
+ }[]): void;
1011
+ ngOnDestroy(): void;
1012
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<AbstractReportScreenV2, never>;
1013
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<AbstractReportScreenV2, "ng-component", never, {}, {}, never, never, true, never>;
284
1014
  }
285
1015
 
286
1016
  declare abstract class AbstractSubScreenComponent extends AbstractScreenComponent {
287
1017
  protected readonly screenState: null;
288
1018
  ngOnInit(): void;
289
1019
  ngAfterViewInit(): void;
290
- static ɵfac: i0.ɵɵFactoryDeclaration<AbstractSubScreenComponent, never>;
291
- static ɵcmp: i0.ɵɵComponentDeclaration<AbstractSubScreenComponent, "ng-component", never, {}, {}, never, never, true, never>;
1020
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<AbstractSubScreenComponent, never>;
1021
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<AbstractSubScreenComponent, "ng-component", never, {}, {}, never, never, true, never>;
1022
+ }
1023
+
1024
+ /**
1025
+ * Signal-first base for SUB-screens — self-contained fragments embedded
1026
+ * inside a host screen (dashboard strips, side panels, …) rather than
1027
+ * routed on their own. The V2 counterpart to the legacy
1028
+ * {@link AbstractSubScreenComponent}.
1029
+ *
1030
+ * A sub-screen typically borrows ANOTHER screen's identity: its config
1031
+ * sets `SCREEN` to the code whose grants gate the data it shows (e.g. a
1032
+ * recent-orders strip on the sales dashboard uses `'SalesOrders'`), so
1033
+ * `canRead()` and `ScreenContext` permissions line up with the backend
1034
+ * seed without any cross-screen plumbing in the component.
1035
+ *
1036
+ * `ScreenConfig` surface (same fields as list screens, reused here):
1037
+ * - `SCREEN` — screen code whose grants apply to this fragment
1038
+ * - `SERVICE` — NSwag client resolved into `serviceInstance`
1039
+ * - `SEARCH_REFERENTIALS_KEYS` / `SEARCH_STATIC_LISTS` — ref-data the
1040
+ * fragment's columns/labels need; loaded on init, `refDataLoaded$`
1041
+ * fires when ready (reference columns resolve reactively, so data
1042
+ * fetches don't have to wait for it)
1043
+ *
1044
+ * No criteria caching, no routing, no toolbar — a sub-screen renders one
1045
+ * `ef-card` (or similar) and owns only its data + collapse state.
1046
+ */
1047
+ declare abstract class AbstractSubScreenV2 extends AbstractScreenComponent implements OnInit {
1048
+ protected readonly screenState: null;
1049
+ protected readonly injector: Injector;
1050
+ /** NSwag client resolved from `ScreenConfig.SERVICE` (null-safe: a
1051
+ * sub-screen fed entirely by inputs may omit SERVICE). */
1052
+ protected serviceInstance: any;
1053
+ /**
1054
+ * Collapse state, bound `[(collapsed)]` on the sub-screen's `ef-card`.
1055
+ * Starts collapsed: sub-screens are secondary content on their host
1056
+ * screen, so they open on demand. Subclasses that must start open set
1057
+ * `this.collapsed.set(false)` in their constructor.
1058
+ */
1059
+ readonly collapsed: _angular_core.WritableSignal<boolean>;
1060
+ toggleCollapsed(): void;
1061
+ /** Read grant on the config's `SCREEN` — gate the whole fragment on
1062
+ * this so an unauthorized user gets nothing (not an erroring card). */
1063
+ canRead(): boolean;
1064
+ ngOnInit(): void;
1065
+ ngOnDestroy(): void;
1066
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<AbstractSubScreenV2, never>;
1067
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<AbstractSubScreenV2, "ng-component", never, {}, {}, never, never, true, never>;
1068
+ }
1069
+
1070
+ /**
1071
+ * Abstract contract for the change-history (audit trail) backend client used by
1072
+ * {@link AbstractDetailScreenV2}. Apps provide their NSwag-generated `AuditClient`
1073
+ * via the {@link AUDIT_HISTORY_SERVICE} token — kept abstract so `@elasticias/screens`
1074
+ * stays decoupled from any app's generated API client.
1075
+ */
1076
+ interface AuditHistoryService {
1077
+ /**
1078
+ * Fetch the change-history for a single record, newest first.
1079
+ * @param entityType Stable backend entity type discriminator (e.g. `'Client'`).
1080
+ * @param id Record identifier.
1081
+ */
1082
+ get(entityType: string, id: string): Observable<any[]>;
292
1083
  }
1084
+ /**
1085
+ * Injection token for the change-history client. Provide it once at app root:
1086
+ *
1087
+ * @example
1088
+ * { provide: AUDIT_HISTORY_SERVICE, useExisting: AuditClient }
1089
+ *
1090
+ * Detail screens then only declare `static AUDIT_ENTITY_TYPE = 'Client'` on
1091
+ * their config — {@link AbstractDetailScreenV2} loads the history automatically
1092
+ * into its `auditEntries` signal.
1093
+ */
1094
+ declare const AUDIT_HISTORY_SERVICE: InjectionToken<AuditHistoryService>;
293
1095
 
294
1096
  declare class ViewModelEntity extends AbstractEntity {
295
1097
  constructor(entity: Record<string, unknown>);
296
1098
  }
297
1099
 
298
- export { AbstractComponent, AbstractDetailScreenComponent, AbstractEntity, AbstractScreenComponent, AbstractSearchScreenComponent, AbstractSubScreenComponent, PaginationEnum, SCREEN_REF_DATA_SERVICE, ScreenConfig, ScreenContext, ScreenStateEnum, SearchEntity, SortDirectionEnum, StateUtilsEnum, ViewModelEntity };
299
- export type { DefaultSort, LoadOptions, ReferenceDataProvider, ScreenDatatableColumn, ScreenReferenceDataService };
1100
+ /**
1101
+ * Declarative custom action consumed by `ef-search-toolbar`'s
1102
+ * `[customActions]` input AND produced by
1103
+ * `AbstractSearchScreenV2.getCustomActions()` (when wired).
1104
+ *
1105
+ * Shape-identical to {@link EfDetailToolbarAction} — kept as a
1106
+ * distinct alias so the search-toolbar can diverge later (e.g.
1107
+ * bulk-only actions, selection-aware visibility) without churning
1108
+ * every detail-screen call site.
1109
+ */
1110
+ type EfSearchToolbarAction = EfDetailToolbarAction;
1111
+
1112
+ export { AUDIT_HISTORY_SERVICE, AbstractComponent, AbstractDetailScreenComponent, AbstractDetailScreenV2, AbstractEntity, AbstractReportScreenV2, AbstractScreenComponent, AbstractSearchScreenComponent, AbstractSearchScreenV2, AbstractSubScreenComponent, AbstractSubScreenV2, PaginationEnum, SCREEN_REF_DATA_SERVICE, ScreenConfig, ScreenContext, ScreenStateEnum, SearchEntity, SortDirectionEnum, StateUtilsEnum, ViewModelEntity };
1113
+ export type { ActiveFilter, AdvancedSelectFilter, AuditHistoryService, DefaultSort, EfDataCardColumn, EfDataCardColumnAlign, EfDataCardColumnType, EfDataCardSort, EfDataCardSortDirection, EfDatePresetKey, EfDateRange, EfDetailToolbarAction, EfReferenceColumnOpts, EfSearchToolbarAction, LoadOptions, ReferenceDataProvider, ReportWidget, ReportWidgetStatus, ScreenDatatableColumn, ScreenReferenceDataService };