@elasticias/screens 1.0.41 → 1.0.42

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.
@@ -458,6 +458,13 @@ class AbstractScreenComponent extends AbstractComponent {
458
458
  const v = raw;
459
459
  if (!v || typeof v !== 'object' || !v.presetKey)
460
460
  return null;
461
+ // An open range stores null bounds. `new Date(null)` is 1970, not
462
+ // NaN, so it must be handled before parsing, not after.
463
+ if (v.start == null && v.end == null) {
464
+ return { start: null, end: null, presetKey: v.presetKey, label: v.label ?? '', labelKey: v.labelKey };
465
+ }
466
+ if (v.start == null || v.end == null)
467
+ return null;
461
468
  const start = new Date(v.start);
462
469
  const end = new Date(v.end);
463
470
  if (isNaN(start.getTime()) || isNaN(end.getTime()))
@@ -1075,14 +1082,27 @@ class AbstractSearchScreenV2 extends AbstractScreenComponent {
1075
1082
  criteria = signal(new SearchEntity({}), ...(ngDevMode ? [{ debugName: "criteria" }] : /* istanbul ignore next */ []));
1076
1083
  /**
1077
1084
  * Active date-range filter shown in `ef-datepicker-advanced`'s
1078
- * trigger. Display-only at construction time — actually filters
1079
- * the search once `onDateRangeChange()` fires (or a screen wires
1080
- * one in `ngOnInit`).
1085
+ * trigger, and the single source of the criteria's `start`/`end`.
1086
+ * The first search, `clear()` and a picked range all send exactly
1087
+ * what this holds, and a cache restore rebuilds it from the restored
1088
+ * dates, so the chip never reads one period while the list shows
1089
+ * another.
1081
1090
  *
1082
- * Override `buildDefaultDateRange()` per screen to ship a
1083
- * different starting preset.
1091
+ * Defaults to `'all_time'` (no dates sent). Override
1092
+ * `buildDefaultDateRange()` per screen to open on a window instead.
1084
1093
  */
1085
1094
  dateRange = signal(this.buildDefaultDateRange(), ...(ngDevMode ? [{ debugName: "dateRange" }] : /* istanbul ignore next */ []));
1095
+ /**
1096
+ * Whether this screen offers the date chip at all. Set it to `false`
1097
+ * (`override readonly showDateFilter = false;`) on a screen whose
1098
+ * backend query has no date field, and guard the template's
1099
+ * `<ef-datepicker-advanced>` with `@if (showDateFilter)` (or drop it).
1100
+ * The base then never sends `start`/`end`: not on first load, not
1101
+ * after `clear()`, not from criteria cached by an older session, and
1102
+ * not from `onDateRangeChange()` / `setDateRange()`, which become
1103
+ * no-ops, so a picker left in the template cannot filter either.
1104
+ */
1105
+ showDateFilter = true;
1086
1106
  /* ── Filter-bar UI state (DRY) ──────────────────────────────────
1087
1107
  Identical across every list screen — lifted up so subclasses
1088
1108
  don't re-declare. `clear()` resets all four signals. */
@@ -1196,14 +1216,16 @@ class AbstractSearchScreenV2 extends AbstractScreenComponent {
1196
1216
  }
1197
1217
  /* ── Date-range filter (DRY) ────────────────────────────────────
1198
1218
  Wired to ef-datepicker-advanced. The default range is
1199
- `last_30_days` — override `buildDefaultDateRange()` per screen
1200
- if a different starting preset is needed. */
1219
+ `all_time`, which sends no dates. Override
1220
+ `buildDefaultDateRange()` per screen if a window is needed. */
1201
1221
  /**
1202
1222
  * Wired to `<ef-datepicker-advanced (rangeChange)>` — stores the
1203
1223
  * range for trigger display and pushes it into the search criteria
1204
1224
  * so the next `search()` filters by it.
1205
1225
  */
1206
1226
  onDateRangeChange(range) {
1227
+ if (!this.showDateFilter)
1228
+ return;
1207
1229
  this.dateRange.set(range);
1208
1230
  this.setDateRange(range.start, range.end);
1209
1231
  }
@@ -1216,19 +1238,52 @@ class AbstractSearchScreenV2 extends AbstractScreenComponent {
1216
1238
  this.dateRange.set(this.buildDefaultDateRange());
1217
1239
  }
1218
1240
  /**
1219
- * Build the default `EfDateRange`. Override per screen to ship a
1220
- * different default — e.g., a 90-day window for low-velocity
1221
- * catalogues. Default: last 30 days.
1241
+ * Build the default `EfDateRange`. Default: `all_time`, no bounds,
1242
+ * so a typed search finds an old record instead of silently missing
1243
+ * it behind a window nobody chose. Override per screen to open on a
1244
+ * window (e.g. `daysAgo(89)` to `startOfToday()` for `last_90_days`);
1245
+ * the first search and `clear()` then send that window.
1222
1246
  */
1223
1247
  buildDefaultDateRange() {
1224
1248
  return {
1225
- start: this.daysAgo(29),
1226
- end: this.startOfToday(),
1227
- presetKey: 'last_30_days',
1228
- labelKey: 'date_preset_last_30_days',
1229
- label: '30 derniers jours',
1249
+ start: null,
1250
+ end: null,
1251
+ presetKey: 'all_time',
1252
+ labelKey: 'date_preset_all_time',
1253
+ label: '',
1230
1254
  };
1231
1255
  }
1256
+ /** Copy the chip's range onto fresh criteria, so what is shown is
1257
+ * what is sent. Sends nothing when the screen has no date chip. */
1258
+ seedDateCriteria(criteria) {
1259
+ const range = this.showDateFilter ? this.dateRange() : null;
1260
+ criteria.start = range?.start ?? null;
1261
+ criteria.end = range?.end ?? null;
1262
+ }
1263
+ /** Rebuild the chip from restored criteria: both dates are shown as
1264
+ * the custom range they filter on; anything less is `all_time`, and
1265
+ * `bootstrapInitialSearch()` drops a lone bound so nothing is sent.
1266
+ * No literal label: the picker formats a custom range itself. */
1267
+ dateRangeFromCriteria(startRaw, endRaw) {
1268
+ const start = this.toDateOrNull(startRaw);
1269
+ const end = this.toDateOrNull(endRaw);
1270
+ if (!start || !end) {
1271
+ return {
1272
+ start: null,
1273
+ end: null,
1274
+ presetKey: 'all_time',
1275
+ labelKey: 'date_preset_all_time',
1276
+ label: '',
1277
+ };
1278
+ }
1279
+ return { start, end, presetKey: 'custom', label: '' };
1280
+ }
1281
+ toDateOrNull(raw) {
1282
+ if (raw == null || raw === '')
1283
+ return null;
1284
+ const d = raw instanceof Date ? raw : new Date(raw);
1285
+ return isNaN(d.getTime()) ? null : d;
1286
+ }
1232
1287
  /** Today at 00:00 local time. */
1233
1288
  startOfToday() {
1234
1289
  const d = new Date();
@@ -1430,7 +1485,17 @@ class AbstractSearchScreenV2 extends AbstractScreenComponent {
1430
1485
  bootstrapInitialSearch() {
1431
1486
  const cached = this.cacheService.getCache(this.screenStateKey);
1432
1487
  if (cached) {
1433
- this.criteria.set(new SearchEntity(cached));
1488
+ const restored = new SearchEntity(cached);
1489
+ const bothBounds = this.toDateOrNull(cached['start']) && this.toDateOrNull(cached['end']);
1490
+ if (!this.showDateFilter || !bothBounds) {
1491
+ // Criteria cached while this screen still had a chip may carry
1492
+ // dates its backend ignores at best; and a lone bound would be
1493
+ // sent under a chip reading "all time". Drop them.
1494
+ restored.start = null;
1495
+ restored.end = null;
1496
+ restored.startEndDateRanges = null;
1497
+ }
1498
+ this.criteria.set(restored);
1434
1499
  this.restoreFilterUiFromCriteria(cached);
1435
1500
  }
1436
1501
  else {
@@ -1448,6 +1513,7 @@ class AbstractSearchScreenV2 extends AbstractScreenComponent {
1448
1513
  pageNumber: PaginationEnum.DEFAULT_PAGE,
1449
1514
  pageSize: PaginationEnum.DEFAULT_PAGE_SIZE,
1450
1515
  };
1516
+ this.seedDateCriteria(fresh);
1451
1517
  this.criteria.set(fresh);
1452
1518
  }
1453
1519
  this.search();
@@ -1458,11 +1524,15 @@ class AbstractSearchScreenV2 extends AbstractScreenComponent {
1458
1524
  * what the drawer / search box displays matches what the search will
1459
1525
  * actually send (otherwise a stale criteria filter keeps applying
1460
1526
  * while every control reads "Tous"). Base handles the free-text
1461
- * input and the declared advanced filters; override (calling super)
1462
- * to sync screen-specific state — status pill, custom switches, ….
1527
+ * input, the date chip and the declared advanced filters; override
1528
+ * (calling super) to sync screen-specific state, such as a status pill
1529
+ * or custom switches.
1463
1530
  */
1464
1531
  restoreFilterUiFromCriteria(cached) {
1465
1532
  this.searchQuery.set(cached['searchText'] ?? '');
1533
+ if (this.showDateFilter) {
1534
+ this.dateRange.set(this.dateRangeFromCriteria(cached['start'], cached['end']));
1535
+ }
1466
1536
  const next = {};
1467
1537
  for (const f of this.advancedFilters) {
1468
1538
  const value = cached[f.key];
@@ -1535,6 +1605,8 @@ class AbstractSearchScreenV2 extends AbstractScreenComponent {
1535
1605
  this.search();
1536
1606
  }
1537
1607
  setDateRange(start, end) {
1608
+ if (!this.showDateFilter)
1609
+ return;
1538
1610
  this.criteria.update((c) => this.cloneCriteria(c, { start, end, page: 1 }));
1539
1611
  this.search();
1540
1612
  }
@@ -1557,6 +1629,8 @@ class AbstractSearchScreenV2 extends AbstractScreenComponent {
1557
1629
  },
1558
1630
  ];
1559
1631
  }
1632
+ this.resetDateRange();
1633
+ this.seedDateCriteria(fresh);
1560
1634
  this.criteria.set(fresh);
1561
1635
  // Reset all the shared filter-bar / selection signals so
1562
1636
  // subclasses don't have to track each one individually.
@@ -1566,7 +1640,6 @@ class AbstractSearchScreenV2 extends AbstractScreenComponent {
1566
1640
  this.activeFilters.set([]);
1567
1641
  this.advancedValues.set({});
1568
1642
  this.selected.set(new Set());
1569
- this.resetDateRange();
1570
1643
  this.cacheService.setCache(this.screenStateKey, fresh);
1571
1644
  this.search();
1572
1645
  }
@@ -2401,7 +2474,7 @@ class AbstractReportScreenV2 extends AbstractScreenComponent {
2401
2474
  this.reportsService = this.injector.get(this.getConfig().SERVICE);
2402
2475
  // Reinstate the period the user last chose before anything loads, so the
2403
2476
  // widgets fetch it once instead of fetching the default and refetching.
2404
- this.dateRange.set(this.restorePersistedDateRange(this.dateRange(), key => this.rangeFromPreset(key)));
2477
+ this.dateRange.set(this.boundedOrDefault(this.restorePersistedDateRange(this.dateRange(), key => this.rangeFromPreset(key))));
2405
2478
  const lists = this.getConfig()?.REPORT_STATIC_LISTS ?? [];
2406
2479
  if (lists.length > 0) {
2407
2480
  this.initializeStaticLists(lists);
@@ -2415,10 +2488,22 @@ class AbstractReportScreenV2 extends AbstractScreenComponent {
2415
2488
  /* ── Period ─────────────────────────────────────────────────── */
2416
2489
  /** Wired to `<ef-datepicker-advanced (rangeChange)>`. */
2417
2490
  onDateRangeChange(range) {
2418
- this.dateRange.set(range);
2419
- this.persistDateRange(range);
2491
+ const bounded = this.boundedOrDefault(range);
2492
+ this.dateRange.set(bounded);
2493
+ this.persistDateRange(bounded);
2420
2494
  this.loadAll();
2421
2495
  }
2496
+ /**
2497
+ * Every widget query is a period, so an open range (`all_time`) has no
2498
+ * meaning here. The picker only offers it with `allowAllTime`, which a
2499
+ * report should not set; if one arrives anyway, fall back to the
2500
+ * default period rather than send a null bound.
2501
+ */
2502
+ boundedOrDefault(range) {
2503
+ return range.start && range.end
2504
+ ? range
2505
+ : this.buildDefaultDateRange();
2506
+ }
2422
2507
  /** Default period from the config's `DEFAULT_PERIOD` preset. */
2423
2508
  buildDefaultDateRange() {
2424
2509
  return this.rangeFromPreset(this.getConfig()?.DEFAULT_PERIOD ?? 'last_30_days');