@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
|
|
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 {
|
|
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:
|
|
25
|
-
static ɵcmp:
|
|
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:
|
|
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:
|
|
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:
|
|
194
|
-
static ɵcmp:
|
|
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:
|
|
239
|
-
static ɵcmp:
|
|
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:
|
|
283
|
-
static ɵcmp:
|
|
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:
|
|
291
|
-
static ɵcmp:
|
|
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
|
-
|
|
299
|
-
|
|
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 };
|