@elasticias/screens 1.0.27 → 1.0.29

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.
@@ -362,6 +362,15 @@ class AbstractScreenComponent extends AbstractComponent {
362
362
  async invalidateReferences(keys) {
363
363
  await this.refDataService.invalidateKeys(keys);
364
364
  }
365
+ /** The record the user acted on carries no id — it was removed by someone
366
+ * else, the list is stale, or the route points at nothing. Say that,
367
+ * rather than the internal "Item [id] is undefined!" this used to ship
368
+ * to end users. Lives on the common base so every screen abstract
369
+ * (search, detail and sub in V1 and V2, plus report-v2) says the same
370
+ * thing — four copies of one string is how this file set drifts. */
371
+ recordNotFound() {
372
+ this.toastService.show({ severity: 'error', textKey: 'ef_error_record_not_found' });
373
+ }
365
374
  handleErrors(errors) {
366
375
  if (errors && errors.length > 0) {
367
376
  errors.forEach(error => this.toastService.showError(error, 'Erreur de validation'));
@@ -637,24 +646,37 @@ class AbstractDetailScreenComponent extends AbstractScreenComponent {
637
646
  print() {
638
647
  // Override in subclass to implement print functionality
639
648
  }
649
+ /**
650
+ * Name of the record being deleted, so the confirmation can say what
651
+ * it is about to destroy rather than asking about "this item".
652
+ * Override in the screen and return whatever a user recognises the
653
+ * record by — `return this.entity?.name;` is the usual body.
654
+ */
655
+ getDeleteConfirmName() {
656
+ return undefined;
657
+ }
640
658
  delete() {
641
- if (!this.entityId)
642
- return this.toastService.showError('Item [id] is undefined!');
643
- this.confirmDialogService.confirm('Êtes-vous sûr de vouloir supprimer ?', () => this.serviceInstance.delete(this.entityId).subscribe({
644
- next: (result) => {
645
- if (result && result.errors && result.errors.length > 0) {
646
- this.handleErrors(result.errors);
647
- }
648
- else {
649
- this.toastService.showSuccess();
650
- this.navigateBack();
651
- }
652
- },
653
- error: () => {
654
- // Errors handled by global error handler
655
- },
656
- }), () => {
657
- // User cancelled deletion
659
+ if (!this.entityId) {
660
+ this.recordNotFound();
661
+ return;
662
+ }
663
+ this.confirmDialogService.confirm({
664
+ intent: 'delete',
665
+ name: this.getDeleteConfirmName(),
666
+ accept: () => this.serviceInstance.delete(this.entityId).subscribe({
667
+ next: (result) => {
668
+ if (result && result.errors && result.errors.length > 0) {
669
+ this.handleErrors(result.errors);
670
+ }
671
+ else {
672
+ this.toastService.showSuccess();
673
+ this.navigateBack();
674
+ }
675
+ },
676
+ error: () => {
677
+ // Errors handled by global error handler
678
+ },
679
+ }),
658
680
  });
659
681
  }
660
682
  navigateBack() {
@@ -893,34 +915,53 @@ class AbstractSearchScreenComponent extends AbstractScreenComponent {
893
915
  }
894
916
  edit(id) {
895
917
  if (!id) {
896
- this.toastService.showError('Item [id] is undefined !');
918
+ this.recordNotFound();
897
919
  return;
898
920
  }
899
921
  this.router.navigate([this.currentUrl.concat('/details/'), id]);
900
922
  }
923
+ /**
924
+ * Name of the row being deleted, so the confirmation can say what it
925
+ * is about to destroy rather than asking about "this item". Override
926
+ * in the screen — look the row up in the loaded results and return
927
+ * whatever a user would recognise it by.
928
+ *
929
+ * ```ts
930
+ * protected override getDeleteConfirmName(id: any) {
931
+ * return this.searchEntity.items?.find((r: any) => r.id === id)?.name;
932
+ * }
933
+ * ```
934
+ */
935
+ getDeleteConfirmName(_id) {
936
+ return undefined;
937
+ }
901
938
  delete(id) {
902
- if (!id)
903
- return this.toastService.showError('Item [id] is undefined!');
904
- this.confirmDialogService.confirm('Êtes-vous sûr de vouloir supprimer ?', () => this.serviceInstance.delete(id).subscribe({
905
- next: (result) => {
906
- if (result && result.errors && result.errors.length > 0) {
907
- this.handleErrors(result.errors);
908
- }
909
- else {
910
- this.search();
911
- this.toastService.showSuccess();
912
- }
913
- },
914
- error: () => {
915
- // Errors handled by global error handler
916
- },
917
- }), () => {
918
- // User cancelled deletion
939
+ if (!id) {
940
+ this.recordNotFound();
941
+ return;
942
+ }
943
+ this.confirmDialogService.confirm({
944
+ intent: 'delete',
945
+ name: this.getDeleteConfirmName(id),
946
+ accept: () => this.serviceInstance.delete(id).subscribe({
947
+ next: (result) => {
948
+ if (result && result.errors && result.errors.length > 0) {
949
+ this.handleErrors(result.errors);
950
+ }
951
+ else {
952
+ this.search();
953
+ this.toastService.showSuccess();
954
+ }
955
+ },
956
+ error: () => {
957
+ // Errors handled by global error handler
958
+ },
959
+ }),
919
960
  });
920
961
  }
921
962
  duplicate(id) {
922
963
  if (!id) {
923
- this.toastService.showError('Item [id] is undefined !');
964
+ this.recordNotFound();
924
965
  return;
925
966
  }
926
967
  this.router.navigate([this.currentUrl.concat('/details/'), id], { queryParams: { mode: 'duplicate' } });
@@ -1327,6 +1368,15 @@ class AbstractSearchScreenV2 extends AbstractScreenComponent {
1327
1368
  const cfg = this.getConfig();
1328
1369
  const hasDynamicRefs = (cfg?.SEARCH_REFERENTIALS_KEYS?.length ?? 0) > 0;
1329
1370
  const hasStaticLists = (cfg?.SEARCH_STATIC_LISTS?.length ?? 0) > 0;
1371
+ // Busy from the first frame, not from the first request.
1372
+ //
1373
+ // A screen with referentials waits for them before it searches at all, and
1374
+ // `loading` used to stay false for that entire window — so the table spent
1375
+ // it showing "nothing here yet" to someone whose data was still being
1376
+ // fetched. Six of the twelve list screens take that branch, including the
1377
+ // one where this was measured at 910ms. `search()` sets the same flag
1378
+ // again when it finally runs, so setting it here is idempotent.
1379
+ this.loading.set(true);
1330
1380
  if (hasDynamicRefs || hasStaticLists) {
1331
1381
  if (hasDynamicRefs)
1332
1382
  this.initializeReferenceKeys(cfg.SEARCH_REFERENTIALS_KEYS);
@@ -1648,35 +1698,54 @@ class AbstractSearchScreenV2 extends AbstractScreenComponent {
1648
1698
  }
1649
1699
  edit(id) {
1650
1700
  if (!id) {
1651
- this.toastService.showError('Item [id] is undefined !');
1701
+ this.recordNotFound();
1652
1702
  return;
1653
1703
  }
1654
1704
  this.router.navigate([`${this.resolveListUrl()}/details`, id]);
1655
1705
  }
1706
+ /**
1707
+ * Name of the row being deleted, so the confirmation can say what it
1708
+ * is about to destroy — "Delete ANDALOCY PARFUMS?" rather than
1709
+ * "Delete?". Override in the screen; returning `undefined` keeps the
1710
+ * generic message, which still reads correctly.
1711
+ *
1712
+ * ```ts
1713
+ * protected override getDeleteConfirmName(id: any) {
1714
+ * return this.items().find(r => r.id === id)?.name;
1715
+ * }
1716
+ * ```
1717
+ */
1718
+ getDeleteConfirmName(_id) {
1719
+ return undefined;
1720
+ }
1656
1721
  delete(id) {
1657
1722
  if (!id) {
1658
- this.toastService.showError('Item [id] is undefined!');
1723
+ this.recordNotFound();
1659
1724
  return;
1660
1725
  }
1661
- this.confirmDialogService.confirm('Êtes-vous sûr de vouloir supprimer ?', () => this.serviceInstance.delete(id).subscribe({
1662
- next: (result) => {
1663
- if (result?.errors?.length) {
1664
- this.handleErrors(result.errors);
1665
- }
1666
- else {
1667
- this.search();
1668
- this.toastService.showSuccess();
1669
- }
1670
- },
1671
- // The HTTP error interceptor surfaces the message (toast). Swallow
1672
- // here so a rejected delete (e.g. a 422 business-rule violation)
1673
- // doesn't bubble up as an unhandled error.
1674
- error: () => undefined,
1675
- }), () => undefined);
1726
+ this.confirmDialogService.confirm({
1727
+ intent: 'delete',
1728
+ name: this.getDeleteConfirmName(id),
1729
+ accept: () => this.serviceInstance.delete(id).subscribe({
1730
+ next: (result) => {
1731
+ if (result?.errors?.length) {
1732
+ this.handleErrors(result.errors);
1733
+ }
1734
+ else {
1735
+ this.search();
1736
+ this.toastService.showSuccess();
1737
+ }
1738
+ },
1739
+ // The HTTP error interceptor surfaces the message (toast). Swallow
1740
+ // here so a rejected delete (e.g. a 422 business-rule violation)
1741
+ // doesn't bubble up as an unhandled error.
1742
+ error: () => undefined,
1743
+ }),
1744
+ });
1676
1745
  }
1677
1746
  duplicate(id) {
1678
1747
  if (!id) {
1679
- this.toastService.showError('Item [id] is undefined !');
1748
+ this.recordNotFound();
1680
1749
  return;
1681
1750
  }
1682
1751
  this.router.navigate([`${this.resolveListUrl()}/details`, id], {
@@ -1954,27 +2023,46 @@ class AbstractDetailScreenV2 extends AbstractScreenComponent {
1954
2023
  /**
1955
2024
  * Soft-delete-then-navigate-back. Confirms with the user first.
1956
2025
  */
2026
+ /**
2027
+ * Name of the record being deleted, so the confirmation can say what
2028
+ * it is about to destroy — "Delete ANDALOCY PARFUMS?" rather than
2029
+ * "Delete?". Override in the screen; returning `undefined` keeps the
2030
+ * generic message, which still reads correctly.
2031
+ *
2032
+ * ```ts
2033
+ * protected override getDeleteConfirmName() {
2034
+ * return this.entity()?.name;
2035
+ * }
2036
+ * ```
2037
+ */
2038
+ getDeleteConfirmName() {
2039
+ return undefined;
2040
+ }
1957
2041
  delete() {
1958
2042
  const id = this.entityId();
1959
2043
  if (!id) {
1960
- this.toastService.showError('Item [id] is undefined!');
2044
+ this.recordNotFound();
1961
2045
  return;
1962
2046
  }
1963
- this.confirmDialogService.confirm('Êtes-vous sûr de vouloir supprimer ?', () => this.serviceInstance.delete(id).subscribe({
1964
- next: (result) => {
1965
- if (result?.errors?.length) {
1966
- this.handleErrors(result.errors);
1967
- }
1968
- else {
1969
- this.toastService.showSuccess();
1970
- this.navigateBack();
1971
- }
1972
- },
1973
- // The HTTP error interceptor surfaces the message (toast).
1974
- // Swallow here so a rejected delete (e.g. a 422 business-rule
1975
- // violation) doesn't bubble up as an unhandled error.
1976
- error: () => undefined,
1977
- }), () => undefined);
2047
+ this.confirmDialogService.confirm({
2048
+ intent: 'delete',
2049
+ name: this.getDeleteConfirmName(),
2050
+ accept: () => this.serviceInstance.delete(id).subscribe({
2051
+ next: (result) => {
2052
+ if (result?.errors?.length) {
2053
+ this.handleErrors(result.errors);
2054
+ }
2055
+ else {
2056
+ this.toastService.showSuccess();
2057
+ this.navigateBack();
2058
+ }
2059
+ },
2060
+ // The HTTP error interceptor surfaces the message (toast).
2061
+ // Swallow here so a rejected delete (e.g. a 422 business-rule
2062
+ // violation) doesn't bubble up as an unhandled error.
2063
+ error: () => undefined,
2064
+ }),
2065
+ });
1978
2066
  }
1979
2067
  /** Navigate to `/details/:id?mode=duplicate` so the abstract can
1980
2068
  * reload the source entity, treat it as a template, and persist