@elasticias/screens 1.0.27 → 1.0.28

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.
@@ -637,26 +637,45 @@ class AbstractDetailScreenComponent extends AbstractScreenComponent {
637
637
  print() {
638
638
  // Override in subclass to implement print functionality
639
639
  }
640
+ /**
641
+ * Name of the record being deleted, so the confirmation can say what
642
+ * it is about to destroy rather than asking about "this item".
643
+ * Override in the screen and return whatever a user recognises the
644
+ * record by — `return this.entity?.name;` is the usual body.
645
+ */
646
+ getDeleteConfirmName() {
647
+ return undefined;
648
+ }
640
649
  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
650
+ if (!this.entityId) {
651
+ this.recordNotFound();
652
+ return;
653
+ }
654
+ this.confirmDialogService.confirm({
655
+ intent: 'delete',
656
+ name: this.getDeleteConfirmName(),
657
+ accept: () => this.serviceInstance.delete(this.entityId).subscribe({
658
+ next: (result) => {
659
+ if (result && result.errors && result.errors.length > 0) {
660
+ this.handleErrors(result.errors);
661
+ }
662
+ else {
663
+ this.toastService.showSuccess();
664
+ this.navigateBack();
665
+ }
666
+ },
667
+ error: () => {
668
+ // Errors handled by global error handler
669
+ },
670
+ }),
658
671
  });
659
672
  }
673
+ /** The screen has no record id — it was removed by someone else, or the
674
+ * route is stale. Say that, rather than the internal
675
+ * "Item [id] is undefined!" this used to ship to end users. */
676
+ recordNotFound() {
677
+ this.toastService.show({ severity: 'error', textKey: 'ef_error_record_not_found' });
678
+ }
660
679
  navigateBack() {
661
680
  const currentUrl = this.router.url.split('?')[0];
662
681
  const detailsIndex = currentUrl.indexOf('/details');
@@ -893,34 +912,59 @@ class AbstractSearchScreenComponent extends AbstractScreenComponent {
893
912
  }
894
913
  edit(id) {
895
914
  if (!id) {
896
- this.toastService.showError('Item [id] is undefined !');
915
+ this.recordNotFound();
897
916
  return;
898
917
  }
899
918
  this.router.navigate([this.currentUrl.concat('/details/'), id]);
900
919
  }
920
+ /**
921
+ * Name of the row being deleted, so the confirmation can say what it
922
+ * is about to destroy rather than asking about "this item". Override
923
+ * in the screen — look the row up in the loaded results and return
924
+ * whatever a user would recognise it by.
925
+ *
926
+ * ```ts
927
+ * protected override getDeleteConfirmName(id: any) {
928
+ * return this.searchEntity.items?.find((r: any) => r.id === id)?.name;
929
+ * }
930
+ * ```
931
+ */
932
+ getDeleteConfirmName(_id) {
933
+ return undefined;
934
+ }
901
935
  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
936
+ if (!id) {
937
+ this.recordNotFound();
938
+ return;
939
+ }
940
+ this.confirmDialogService.confirm({
941
+ intent: 'delete',
942
+ name: this.getDeleteConfirmName(id),
943
+ accept: () => this.serviceInstance.delete(id).subscribe({
944
+ next: (result) => {
945
+ if (result && result.errors && result.errors.length > 0) {
946
+ this.handleErrors(result.errors);
947
+ }
948
+ else {
949
+ this.search();
950
+ this.toastService.showSuccess();
951
+ }
952
+ },
953
+ error: () => {
954
+ // Errors handled by global error handler
955
+ },
956
+ }),
919
957
  });
920
958
  }
959
+ /** The row the user acted on carries no id — it was removed by someone
960
+ * else, or the list is stale. Say that, rather than the internal
961
+ * "Item [id] is undefined!" this used to ship to end users. */
962
+ recordNotFound() {
963
+ this.toastService.show({ severity: 'error', textKey: 'ef_error_record_not_found' });
964
+ }
921
965
  duplicate(id) {
922
966
  if (!id) {
923
- this.toastService.showError('Item [id] is undefined !');
967
+ this.recordNotFound();
924
968
  return;
925
969
  }
926
970
  this.router.navigate([this.currentUrl.concat('/details/'), id], { queryParams: { mode: 'duplicate' } });
@@ -1327,6 +1371,15 @@ class AbstractSearchScreenV2 extends AbstractScreenComponent {
1327
1371
  const cfg = this.getConfig();
1328
1372
  const hasDynamicRefs = (cfg?.SEARCH_REFERENTIALS_KEYS?.length ?? 0) > 0;
1329
1373
  const hasStaticLists = (cfg?.SEARCH_STATIC_LISTS?.length ?? 0) > 0;
1374
+ // Busy from the first frame, not from the first request.
1375
+ //
1376
+ // A screen with referentials waits for them before it searches at all, and
1377
+ // `loading` used to stay false for that entire window — so the table spent
1378
+ // it showing "nothing here yet" to someone whose data was still being
1379
+ // fetched. Six of the twelve list screens take that branch, including the
1380
+ // one where this was measured at 910ms. `search()` sets the same flag
1381
+ // again when it finally runs, so setting it here is idempotent.
1382
+ this.loading.set(true);
1330
1383
  if (hasDynamicRefs || hasStaticLists) {
1331
1384
  if (hasDynamicRefs)
1332
1385
  this.initializeReferenceKeys(cfg.SEARCH_REFERENTIALS_KEYS);
@@ -1648,35 +1701,60 @@ class AbstractSearchScreenV2 extends AbstractScreenComponent {
1648
1701
  }
1649
1702
  edit(id) {
1650
1703
  if (!id) {
1651
- this.toastService.showError('Item [id] is undefined !');
1704
+ this.recordNotFound();
1652
1705
  return;
1653
1706
  }
1654
1707
  this.router.navigate([`${this.resolveListUrl()}/details`, id]);
1655
1708
  }
1709
+ /**
1710
+ * Name of the row being deleted, so the confirmation can say what it
1711
+ * is about to destroy — "Delete ANDALOCY PARFUMS?" rather than
1712
+ * "Delete?". Override in the screen; returning `undefined` keeps the
1713
+ * generic message, which still reads correctly.
1714
+ *
1715
+ * ```ts
1716
+ * protected override getDeleteConfirmName(id: any) {
1717
+ * return this.items().find(r => r.id === id)?.name;
1718
+ * }
1719
+ * ```
1720
+ */
1721
+ getDeleteConfirmName(_id) {
1722
+ return undefined;
1723
+ }
1656
1724
  delete(id) {
1657
1725
  if (!id) {
1658
- this.toastService.showError('Item [id] is undefined!');
1726
+ this.recordNotFound();
1659
1727
  return;
1660
1728
  }
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);
1729
+ this.confirmDialogService.confirm({
1730
+ intent: 'delete',
1731
+ name: this.getDeleteConfirmName(id),
1732
+ accept: () => this.serviceInstance.delete(id).subscribe({
1733
+ next: (result) => {
1734
+ if (result?.errors?.length) {
1735
+ this.handleErrors(result.errors);
1736
+ }
1737
+ else {
1738
+ this.search();
1739
+ this.toastService.showSuccess();
1740
+ }
1741
+ },
1742
+ // The HTTP error interceptor surfaces the message (toast). Swallow
1743
+ // here so a rejected delete (e.g. a 422 business-rule violation)
1744
+ // doesn't bubble up as an unhandled error.
1745
+ error: () => undefined,
1746
+ }),
1747
+ });
1748
+ }
1749
+ /** The row the user acted on carries no id — it was removed by someone
1750
+ * else, or the list is stale. Say that, rather than the internal
1751
+ * "Item [id] is undefined!" this used to ship to end users. */
1752
+ recordNotFound() {
1753
+ this.toastService.show({ severity: 'error', textKey: 'ef_error_record_not_found' });
1676
1754
  }
1677
1755
  duplicate(id) {
1678
1756
  if (!id) {
1679
- this.toastService.showError('Item [id] is undefined !');
1757
+ this.recordNotFound();
1680
1758
  return;
1681
1759
  }
1682
1760
  this.router.navigate([`${this.resolveListUrl()}/details`, id], {
@@ -1954,27 +2032,52 @@ class AbstractDetailScreenV2 extends AbstractScreenComponent {
1954
2032
  /**
1955
2033
  * Soft-delete-then-navigate-back. Confirms with the user first.
1956
2034
  */
2035
+ /**
2036
+ * Name of the record being deleted, so the confirmation can say what
2037
+ * it is about to destroy — "Delete ANDALOCY PARFUMS?" rather than
2038
+ * "Delete?". Override in the screen; returning `undefined` keeps the
2039
+ * generic message, which still reads correctly.
2040
+ *
2041
+ * ```ts
2042
+ * protected override getDeleteConfirmName() {
2043
+ * return this.entity()?.name;
2044
+ * }
2045
+ * ```
2046
+ */
2047
+ getDeleteConfirmName() {
2048
+ return undefined;
2049
+ }
1957
2050
  delete() {
1958
2051
  const id = this.entityId();
1959
2052
  if (!id) {
1960
- this.toastService.showError('Item [id] is undefined!');
2053
+ this.recordNotFound();
1961
2054
  return;
1962
2055
  }
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);
2056
+ this.confirmDialogService.confirm({
2057
+ intent: 'delete',
2058
+ name: this.getDeleteConfirmName(),
2059
+ accept: () => this.serviceInstance.delete(id).subscribe({
2060
+ next: (result) => {
2061
+ if (result?.errors?.length) {
2062
+ this.handleErrors(result.errors);
2063
+ }
2064
+ else {
2065
+ this.toastService.showSuccess();
2066
+ this.navigateBack();
2067
+ }
2068
+ },
2069
+ // The HTTP error interceptor surfaces the message (toast).
2070
+ // Swallow here so a rejected delete (e.g. a 422 business-rule
2071
+ // violation) doesn't bubble up as an unhandled error.
2072
+ error: () => undefined,
2073
+ }),
2074
+ });
2075
+ }
2076
+ /** The screen has no record id — it was removed by someone else, or
2077
+ * the route is stale. Say that, rather than the internal
2078
+ * "Item [id] is undefined!" this used to ship to end users. */
2079
+ recordNotFound() {
2080
+ this.toastService.show({ severity: 'error', textKey: 'ef_error_record_not_found' });
1978
2081
  }
1979
2082
  /** Navigate to `/details/:id?mode=duplicate` so the abstract can
1980
2083
  * reload the source entity, treat it as a template, and persist