@memberjunction/ng-entity-viewer 6.1.0-edge.4 → 6.1.0-edge.6

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.
Files changed (46) hide show
  1. package/dist/lib/aggregate-panel/aggregate-panel.component.js +1 -1
  2. package/dist/lib/aggregate-setup-dialog/aggregate-setup-dialog.component.js +1 -1
  3. package/dist/lib/confirm-dialog/confirm-dialog.component.js +1 -1
  4. package/dist/lib/duplicate-view-dialog/duplicate-view-dialog.component.js +1 -1
  5. package/dist/lib/entity-cards/entity-cards.component.d.ts.map +1 -1
  6. package/dist/lib/entity-cards/entity-cards.component.js +20 -32
  7. package/dist/lib/entity-cards/entity-cards.component.js.map +1 -1
  8. package/dist/lib/entity-data-grid/entity-data-grid.component.d.ts +14 -1
  9. package/dist/lib/entity-data-grid/entity-data-grid.component.d.ts.map +1 -1
  10. package/dist/lib/entity-data-grid/entity-data-grid.component.js +253 -194
  11. package/dist/lib/entity-data-grid/entity-data-grid.component.js.map +1 -1
  12. package/dist/lib/entity-record-detail-panel/entity-record-detail-panel.component.js +1 -1
  13. package/dist/lib/entity-viewer/entity-viewer.component.d.ts +5 -0
  14. package/dist/lib/entity-viewer/entity-viewer.component.d.ts.map +1 -1
  15. package/dist/lib/entity-viewer/entity-viewer.component.js +51 -28
  16. package/dist/lib/entity-viewer/entity-viewer.component.js.map +1 -1
  17. package/dist/lib/quick-save-dialog/quick-save-dialog.component.js +1 -1
  18. package/dist/lib/recycle-bin/recycle-bin.component.d.ts.map +1 -1
  19. package/dist/lib/recycle-bin/recycle-bin.component.js +6 -1
  20. package/dist/lib/recycle-bin/recycle-bin.component.js.map +1 -1
  21. package/dist/lib/shared-view-warning-dialog/shared-view-warning-dialog.component.js +1 -1
  22. package/dist/lib/types.d.ts +21 -0
  23. package/dist/lib/types.d.ts.map +1 -1
  24. package/dist/lib/types.js +23 -0
  25. package/dist/lib/types.js.map +1 -1
  26. package/dist/lib/utils/record.util.d.ts.map +1 -1
  27. package/dist/lib/utils/record.util.js +1 -5
  28. package/dist/lib/utils/record.util.js.map +1 -1
  29. package/dist/lib/view-config-panel/view-config-panel.component.js +10 -10
  30. package/dist/lib/view-config-panel/view-config-panel.component.js.map +1 -1
  31. package/dist/lib/view-header/view-header.component.js +1 -1
  32. package/dist/lib/view-type-switcher/view-type-switcher.component.js +1 -1
  33. package/dist/lib/view-types/descriptors/map-view-type.d.ts +3 -2
  34. package/dist/lib/view-types/descriptors/map-view-type.d.ts.map +1 -1
  35. package/dist/lib/view-types/descriptors/map-view-type.js +5 -3
  36. package/dist/lib/view-types/descriptors/map-view-type.js.map +1 -1
  37. package/dist/lib/view-types/renderers/grid-view-renderer.component.d.ts +9 -3
  38. package/dist/lib/view-types/renderers/grid-view-renderer.component.d.ts.map +1 -1
  39. package/dist/lib/view-types/renderers/grid-view-renderer.component.js +24 -20
  40. package/dist/lib/view-types/renderers/grid-view-renderer.component.js.map +1 -1
  41. package/dist/lib/view-types/renderers/timeline-view-renderer.component.d.ts +2 -0
  42. package/dist/lib/view-types/renderers/timeline-view-renderer.component.d.ts.map +1 -1
  43. package/dist/lib/view-types/renderers/timeline-view-renderer.component.js +12 -0
  44. package/dist/lib/view-types/renderers/timeline-view-renderer.component.js.map +1 -1
  45. package/dist/lib/view-workspace/view-workspace.component.js +1 -1
  46. package/package.json +25 -25
@@ -339,7 +339,7 @@ export class QuickSaveDialogComponent {
339
339
  i0.ɵɵconditional(ctx.Summary ? 29 : -1);
340
340
  i0.ɵɵadvance(7);
341
341
  i0.ɵɵconditional(ctx.ViewEntity ? 36 : 37);
342
- } }, dependencies: [i1.DefaultValueAccessor, i1.CheckboxControlValueAccessor, i1.NgControlStatus, i1.NgModel], styles: ["\n\n.dialog-backdrop[_ngcontent-%COMP%] {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.4);\n z-index: 2000;\n animation: _ngcontent-%COMP%_fadeIn 0.2s ease;\n}\n\n@keyframes _ngcontent-%COMP%_fadeIn {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n\n\n.dialog-panel[_ngcontent-%COMP%] {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%) scale(0.95);\n width: 480px;\n max-width: calc(100vw - 40px);\n max-height: calc(100vh - 60px);\n background: var(--mj-bg-surface);\n border-radius: 16px;\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);\n z-index: 2001;\n display: flex;\n flex-direction: column;\n opacity: 0;\n pointer-events: none;\n transition: opacity 0.2s ease, transform 0.2s ease;\n}\n\n.dialog-panel.open[_ngcontent-%COMP%] {\n opacity: 1;\n pointer-events: auto;\n transform: translate(-50%, -50%) scale(1);\n}\n\n\n\n.dialog-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 18px 24px;\n border-bottom: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-card);\n border-radius: 16px 16px 0 0;\n}\n\n.header-title[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n font-size: 17px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.header-title[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n font-size: 18px;\n}\n\n.close-btn[_ngcontent-%COMP%] {\n width: 32px;\n height: 32px;\n border: none;\n background: transparent;\n border-radius: 8px;\n cursor: pointer;\n color: var(--mj-text-muted);\n transition: all 0.15s ease;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.close-btn[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-primary);\n}\n\n\n\n.entity-badge[_ngcontent-%COMP%] {\n font-size: 12px;\n font-weight: 500;\n color: var(--mj-text-muted);\n background: var(--mj-bg-surface-sunken);\n padding: 3px 10px;\n border-radius: 12px;\n margin-left: auto;\n margin-right: 8px;\n}\n\n\n\n.dialog-content[_ngcontent-%COMP%] {\n padding: 20px 24px;\n overflow-y: auto;\n display: flex;\n flex-direction: column;\n gap: 16px;\n}\n\n\n\n.form-group[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.form-group[_ngcontent-%COMP%] label[_ngcontent-%COMP%] {\n font-size: 13px;\n font-weight: 500;\n color: var(--mj-text-primary);\n}\n\n.form-group[_ngcontent-%COMP%] .optional[_ngcontent-%COMP%] {\n font-weight: 400;\n color: var(--mj-text-disabled);\n}\n\n.form-input[_ngcontent-%COMP%] {\n padding: 9px 12px;\n border: 1px solid var(--mj-border-strong);\n border-radius: 8px;\n font-size: 14px;\n color: var(--mj-text-primary);\n transition: border-color 0.15s ease;\n}\n\n.form-input[_ngcontent-%COMP%]:focus {\n outline: none;\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n}\n\n.form-input.invalid[_ngcontent-%COMP%] {\n border-color: var(--mj-status-error);\n}\n\n.form-textarea[_ngcontent-%COMP%] {\n resize: vertical;\n font-family: inherit;\n min-height: 50px;\n}\n\n.validation-error[_ngcontent-%COMP%] {\n font-size: 12px;\n color: var(--mj-status-error);\n}\n\n\n\n.checkbox-label[_ngcontent-%COMP%] {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n cursor: pointer;\n}\n\n.checkbox-label[_ngcontent-%COMP%] input[type=\"checkbox\"][_ngcontent-%COMP%] {\n margin-top: 3px;\n flex-shrink: 0;\n}\n\n.checkbox-text[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.checkbox-text[_ngcontent-%COMP%] strong[_ngcontent-%COMP%] {\n font-size: 13px;\n color: var(--mj-text-primary);\n}\n\n.checkbox-text[_ngcontent-%COMP%] small[_ngcontent-%COMP%] {\n font-size: 12px;\n color: var(--mj-text-disabled);\n}\n\n\n\n.summary-section[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: 10px;\n padding: 14px;\n}\n\n.summary-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 13px;\n font-weight: 500;\n color: var(--mj-text-muted);\n margin-bottom: 10px;\n}\n\n.summary-header[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n}\n\n.summary-items[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.summary-item[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 13px;\n color: var(--mj-text-primary);\n}\n\n.summary-item[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-text-muted);\n width: 16px;\n text-align: center;\n font-size: 12px;\n}\n\n.summary-item.smart-filter[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n}\n\n.summary-item.smart-filter[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n}\n\n\n\n.advanced-link[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 0;\n border: none;\n background: none;\n color: var(--mj-brand-primary);\n font-size: 13px;\n cursor: pointer;\n transition: color 0.15s ease;\n}\n\n.advanced-link[_ngcontent-%COMP%]:hover {\n color: var(--mj-color-info-600);\n text-decoration: underline;\n}\n\n\n\n.dialog-footer[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px 24px;\n border-top: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-card);\n border-radius: 0 0 16px 16px;\n}\n\n.footer-actions[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n\n\n\n.btn[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 9px 20px;\n border: none;\n border-radius: 8px;\n font-size: 14px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n}\n\n.btn-primary[_ngcontent-%COMP%] {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n}\n\n.btn-primary[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: var(--mj-color-info-600);\n}\n\n.btn-primary[_ngcontent-%COMP%]:disabled {\n background: color-mix(in srgb, var(--mj-brand-primary) 50%, var(--mj-bg-surface));\n cursor: not-allowed;\n}\n\n.btn-secondary[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n border: 1px solid var(--mj-border-strong);\n}\n\n.btn-secondary[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: var(--mj-bg-surface-card);\n border-color: var(--mj-text-disabled);\n}\n\n.btn-secondary[_ngcontent-%COMP%]:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.btn-cancel[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-primary);\n}\n\n.btn-cancel[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-active);\n}"] });
342
+ } }, dependencies: [i1.DefaultValueAccessor, i1.CheckboxControlValueAccessor, i1.NgControlStatus, i1.NgModel], styles: ["\n.dialog-backdrop[_ngcontent-%COMP%] {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.4);\n z-index: 2000;\n animation: _ngcontent-%COMP%_fadeIn 0.2s ease;\n}\n\n@keyframes _ngcontent-%COMP%_fadeIn {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n\n.dialog-panel[_ngcontent-%COMP%] {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%) scale(0.95);\n width: 480px;\n max-width: calc(100vw - 40px);\n max-height: calc(100vh - 60px);\n background: var(--mj-bg-surface);\n border-radius: 16px;\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);\n z-index: 2001;\n display: flex;\n flex-direction: column;\n opacity: 0;\n pointer-events: none;\n transition: opacity 0.2s ease, transform 0.2s ease;\n}\n\n.dialog-panel.open[_ngcontent-%COMP%] {\n opacity: 1;\n pointer-events: auto;\n transform: translate(-50%, -50%) scale(1);\n}\n\n\n.dialog-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 18px 24px;\n border-bottom: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-card);\n border-radius: 16px 16px 0 0;\n}\n\n.header-title[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n font-size: 17px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.header-title[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n font-size: 18px;\n}\n\n.close-btn[_ngcontent-%COMP%] {\n width: 32px;\n height: 32px;\n border: none;\n background: transparent;\n border-radius: 8px;\n cursor: pointer;\n color: var(--mj-text-muted);\n transition: all 0.15s ease;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.close-btn[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-primary);\n}\n\n\n.entity-badge[_ngcontent-%COMP%] {\n font-size: 12px;\n font-weight: 500;\n color: var(--mj-text-muted);\n background: var(--mj-bg-surface-sunken);\n padding: 3px 10px;\n border-radius: 12px;\n margin-left: auto;\n margin-right: 8px;\n}\n\n\n.dialog-content[_ngcontent-%COMP%] {\n padding: 20px 24px;\n overflow-y: auto;\n display: flex;\n flex-direction: column;\n gap: 16px;\n}\n\n\n.form-group[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.form-group[_ngcontent-%COMP%] label[_ngcontent-%COMP%] {\n font-size: 13px;\n font-weight: 500;\n color: var(--mj-text-primary);\n}\n\n.form-group[_ngcontent-%COMP%] .optional[_ngcontent-%COMP%] {\n font-weight: 400;\n color: var(--mj-text-disabled);\n}\n\n.form-input[_ngcontent-%COMP%] {\n padding: 9px 12px;\n border: 1px solid var(--mj-border-strong);\n border-radius: 8px;\n font-size: 14px;\n color: var(--mj-text-primary);\n transition: border-color 0.15s ease;\n}\n\n.form-input[_ngcontent-%COMP%]:focus {\n outline: none;\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n}\n\n.form-input.invalid[_ngcontent-%COMP%] {\n border-color: var(--mj-status-error);\n}\n\n.form-textarea[_ngcontent-%COMP%] {\n resize: vertical;\n font-family: inherit;\n min-height: 50px;\n}\n\n.validation-error[_ngcontent-%COMP%] {\n font-size: 12px;\n color: var(--mj-status-error);\n}\n\n\n.checkbox-label[_ngcontent-%COMP%] {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n cursor: pointer;\n}\n\n.checkbox-label[_ngcontent-%COMP%] input[type=\"checkbox\"][_ngcontent-%COMP%] {\n margin-top: 3px;\n flex-shrink: 0;\n}\n\n.checkbox-text[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.checkbox-text[_ngcontent-%COMP%] strong[_ngcontent-%COMP%] {\n font-size: 13px;\n color: var(--mj-text-primary);\n}\n\n.checkbox-text[_ngcontent-%COMP%] small[_ngcontent-%COMP%] {\n font-size: 12px;\n color: var(--mj-text-disabled);\n}\n\n\n.summary-section[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: 10px;\n padding: 14px;\n}\n\n.summary-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 13px;\n font-weight: 500;\n color: var(--mj-text-muted);\n margin-bottom: 10px;\n}\n\n.summary-header[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n}\n\n.summary-items[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.summary-item[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 13px;\n color: var(--mj-text-primary);\n}\n\n.summary-item[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-text-muted);\n width: 16px;\n text-align: center;\n font-size: 12px;\n}\n\n.summary-item.smart-filter[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n}\n\n.summary-item.smart-filter[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n}\n\n\n.advanced-link[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 0;\n border: none;\n background: none;\n color: var(--mj-brand-primary);\n font-size: 13px;\n cursor: pointer;\n transition: color 0.15s ease;\n}\n\n.advanced-link[_ngcontent-%COMP%]:hover {\n color: var(--mj-color-info-600);\n text-decoration: underline;\n}\n\n\n.dialog-footer[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px 24px;\n border-top: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-card);\n border-radius: 0 0 16px 16px;\n}\n\n.footer-actions[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n\n\n.btn[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 9px 20px;\n border: none;\n border-radius: 8px;\n font-size: 14px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n}\n\n.btn-primary[_ngcontent-%COMP%] {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n}\n\n.btn-primary[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: var(--mj-color-info-600);\n}\n\n.btn-primary[_ngcontent-%COMP%]:disabled {\n background: color-mix(in srgb, var(--mj-brand-primary) 50%, var(--mj-bg-surface));\n cursor: not-allowed;\n}\n\n.btn-secondary[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n border: 1px solid var(--mj-border-strong);\n}\n\n.btn-secondary[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: var(--mj-bg-surface-card);\n border-color: var(--mj-text-disabled);\n}\n\n.btn-secondary[_ngcontent-%COMP%]:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.btn-cancel[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-primary);\n}\n\n.btn-cancel[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-active);\n}"] });
343
343
  }
344
344
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(QuickSaveDialogComponent, [{
345
345
  type: Component,
@@ -1 +1 @@
1
- {"version":3,"file":"recycle-bin.component.d.ts","sourceRoot":"","sources":["../../../src/lib/recycle-bin/recycle-bin.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,iBAAiB,EACjB,YAAY,EAEZ,MAAM,EAGP,MAAM,eAAe,CAAC;AACvB,OAAO,EAKL,QAAQ,EACT,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AACvE,OAAO,EACL,2BAA2B,EAC3B,4BAA4B,EAC5B,2BAA2B,EAC3B,4BAA4B,EAC5B,6BAA6B,EAC7B,4BAA4B,EAC5B,eAAe,EAChB,MAAM,6BAA6B,CAAC;;AAErC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AACH,qBAQa,mBAAoB,SAAQ,oBAAqB,YAAW,MAAM;IA+FjE,OAAO,CAAC,GAAG;IA5FvB;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAS;IACzB,IACI,OAAO,CAAC,KAAK,EAAE,OAAO,EAMzB;IACD,IAAI,OAAO,IAAI,OAAO,CAErB;IAED;;OAEG;IACM,UAAU,EAAE,MAAM,GAAG,IAAI,CAAQ;IAE1C;;;OAGG;IACM,WAAW,EAAE,QAAQ,GAAG,IAAI,CAAQ;IAE7C;;;;;OAKG;IACM,UAAU,SAAO;IAI1B,4CAA4C;IAClC,MAAM,qBAA4B;IAE5C;;;;OAIG;IACO,oBAAoB,8CAAqD;IAEnF,sDAAsD;IAC5C,mBAAmB,6CAAoD;IAEjF;;;;;OAKG;IACO,mBAAmB,6CAAoD;IAEjF;;;OAGG;IACO,kBAAkB,4CAAmD;IAE/E;;;;OAIG;IACO,mBAAmB,6CAAoD;IAEjF,6DAA6D;IACnD,kBAAkB,4CAAmD;IAIxE,SAAS,UAAS;IAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAQ;IAChC,OAAO,EAAE,eAAe,EAAE,CAAM;IAEvC,0DAA0D;IACnD,aAAa,EAAE,eAAe,GAAG,IAAI,CAAQ;IAC7C,cAAc,UAAS;IAE9B,+DAA+D;IACxD,SAAS,UAAS;IAClB,SAAS,UAAS;IAEzB,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,kBAAkB,CAA2B;gBAEjC,GAAG,EAAE,iBAAiB;IAG1C,QAAQ,IAAI,IAAI;IAOhB,IAAW,cAAc,IAAI,MAAM,CAIlC;IAED;;;OAGG;IACH,IAAW,SAAS,IAAI,OAAO,CAE9B;IAID;;;;OAIG;IACU,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC;IA4FhD;;;OAGG;IACI,gBAAgB,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI;IAmBrD;;;OAGG;IACU,kBAAkB,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IA+FnE,kBAAkB,IAAI,IAAI;IAM1B,OAAO,IAAI,IAAI;IAQtB;;;;;OAKG;IACH,OAAO,CAAC,qBAAqB;IA2B7B;;;OAGG;IACH,OAAO,CAAC,mBAAmB;IAc3B;;;;OAIG;IACH,OAAO,CAAC,qBAAqB;IAyB7B,OAAO,CAAC,gBAAgB;IAYxB,OAAO,CAAC,aAAa;IAWd,eAAe,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,GAAG,MAAM;IAYnD,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM;yCAtdvC,mBAAmB;2CAAnB,mBAAmB;CA2d/B"}
1
+ {"version":3,"file":"recycle-bin.component.d.ts","sourceRoot":"","sources":["../../../src/lib/recycle-bin/recycle-bin.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,iBAAiB,EACjB,YAAY,EAEZ,MAAM,EAGP,MAAM,eAAe,CAAC;AACvB,OAAO,EAKL,QAAQ,EACT,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AACvE,OAAO,EACL,2BAA2B,EAC3B,4BAA4B,EAC5B,2BAA2B,EAC3B,4BAA4B,EAC5B,6BAA6B,EAC7B,4BAA4B,EAC5B,eAAe,EAChB,MAAM,6BAA6B,CAAC;;AAErC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AACH,qBAQa,mBAAoB,SAAQ,oBAAqB,YAAW,MAAM;IA+FjE,OAAO,CAAC,GAAG;IA5FvB;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAS;IACzB,IACI,OAAO,CAAC,KAAK,EAAE,OAAO,EAMzB;IACD,IAAI,OAAO,IAAI,OAAO,CAErB;IAED;;OAEG;IACM,UAAU,EAAE,MAAM,GAAG,IAAI,CAAQ;IAE1C;;;OAGG;IACM,WAAW,EAAE,QAAQ,GAAG,IAAI,CAAQ;IAE7C;;;;;OAKG;IACM,UAAU,SAAO;IAI1B,4CAA4C;IAClC,MAAM,qBAA4B;IAE5C;;;;OAIG;IACO,oBAAoB,8CAAqD;IAEnF,sDAAsD;IAC5C,mBAAmB,6CAAoD;IAEjF;;;;;OAKG;IACO,mBAAmB,6CAAoD;IAEjF;;;OAGG;IACO,kBAAkB,4CAAmD;IAE/E;;;;OAIG;IACO,mBAAmB,6CAAoD;IAEjF,6DAA6D;IACnD,kBAAkB,4CAAmD;IAIxE,SAAS,UAAS;IAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAQ;IAChC,OAAO,EAAE,eAAe,EAAE,CAAM;IAEvC,0DAA0D;IACnD,aAAa,EAAE,eAAe,GAAG,IAAI,CAAQ;IAC7C,cAAc,UAAS;IAE9B,+DAA+D;IACxD,SAAS,UAAS;IAClB,SAAS,UAAS;IAEzB,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,kBAAkB,CAA2B;gBAEjC,GAAG,EAAE,iBAAiB;IAG1C,QAAQ,IAAI,IAAI;IAOhB,IAAW,cAAc,IAAI,MAAM,CAIlC;IAED;;;OAGG;IACH,IAAW,SAAS,IAAI,OAAO,CAE9B;IAID;;;;OAIG;IACU,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC;IA4FhD;;;OAGG;IACI,gBAAgB,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI;IAmBrD;;;OAGG;IACU,kBAAkB,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IA+FnE,kBAAkB,IAAI,IAAI;IAM1B,OAAO,IAAI,IAAI;IAQtB;;;;;OAKG;IACH,OAAO,CAAC,qBAAqB;IA2B7B;;;OAGG;IACH,OAAO,CAAC,mBAAmB;IAmB3B;;;;OAIG;IACH,OAAO,CAAC,qBAAqB;IAyB7B,OAAO,CAAC,gBAAgB;IAYxB,OAAO,CAAC,aAAa;IAWd,eAAe,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,GAAG,MAAM;IAYnD,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM;yCA3dvC,mBAAmB;2CAAnB,mBAAmB;CAge/B"}
@@ -518,7 +518,12 @@ export class RecycleBinComponent extends BaseAngularComponent {
518
518
  if (typeof v === 'string' && v.trim().length > 0)
519
519
  return v;
520
520
  }
521
- return `Record ${snapshot[entityInfo.PrimaryKeys[0]?.Name] ?? ''}`;
521
+ // Last resort: every primary-key column's value (the entity is arbitrary and may be composite-keyed)
522
+ const keyValues = entityInfo.PrimaryKeys
523
+ .map((pk) => snapshot[pk.Name])
524
+ .filter((v) => v != null && v !== '')
525
+ .map((v) => String(v));
526
+ return `Record ${keyValues.join(', ')}`;
522
527
  }
523
528
  /**
524
529
  * Picks up to 3 "interesting" fields from the snapshot to render as
@@ -1 +1 @@
1
- {"version":3,"file":"recycle-bin.component.js","sourceRoot":"","sources":["../../../src/lib/recycle-bin/recycle-bin.component.ts","../../../src/lib/recycle-bin/recycle-bin.component.html"],"names":[],"mappings":"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","sourcesContent":["import {\n Component,\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n EventEmitter,\n Input,\n OnInit,\n Output,\n ViewEncapsulation,\n} from '@angular/core';\nimport {\n EntityFieldInfo,\n EntityInfo,\n Metadata,\n RunView,\n UserInfo,\n} from '@memberjunction/core';\nimport { MJRecordChangeEntity } from '@memberjunction/core-entities';\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\nimport { RestoreCommitEvent } from '@memberjunction/ng-record-changes';\nimport {\n AfterRecordRestoreEventArgs,\n AfterRecycleBinOpenEventArgs,\n AfterRestoreCommitEventArgs,\n BeforeRecordRestoreEventArgs,\n BeforeRecycleBinOpenEventArgs,\n BeforeRestoreCommitEventArgs,\n RecycleBinEntry,\n} from './events/recycle-bin-events';\n\n/**\n * Slide-in panel that lists all hard-deleted records for a single entity\n * and lets a user with `Delete` permission re-create any of them from its\n * historical RecordChange snapshot.\n *\n * ### When to use\n *\n * Surface this component from any entity-viewing context where the user\n * might need to restore a hard-deleted record. The {@link EntityViewerComponent},\n * {@link EntityDataGridComponent}, and {@link EntityCardsComponent}\n * already expose a `ShowRecycleBin` input (default `true`) that renders a\n * chip in their toolbar — you only need to use this component directly if\n * you're building a custom viewer.\n *\n * ### Permissions\n *\n * The chip / panel is gated on `entity.UserPermissions.CanDelete` —\n * rationale: there is no native \"undelete\" permission in MemberJunction,\n * but if a user has the higher-trust permission to *delete* records of an\n * entity, restoring deleted ones is well within scope. The actual\n * re-create action additionally requires `CanCreate`; without it the\n * Restore button on each card disables with a tooltip.\n *\n * ### Soft deletes vs hard deletes\n *\n * This component only surfaces *hard*-deleted records. Soft-deletes (e.g.,\n * `IsDeleted` flags, `Status='Inactive'`, etc.) leave the record visible\n * in normal entity views, so the standard Record Changes panel + restore\n * preview already handles them — no Recycle Bin needed.\n *\n * ### Cancelable Before/After event surface\n *\n * Every meaningful action emits a paired `before*` / `after*` event. The\n * `before*` event carries `cancel: boolean` so consumers can intercept\n * — useful for custom approval workflows, audit logging, or to take over\n * the actual restore execution entirely. See {@link BeforeRecordRestoreEventArgs}\n * and friends.\n *\n * @example Basic usage (rare — usually embedded by entity-viewer)\n * <mj-recycle-bin\n * [Visible]=\"showBin\"\n * [EntityName]=\"'Customers'\"\n * (Closed)=\"showBin = false\"\n * (BeforeRecordRestore)=\"auditRestore($event)\"\n * (AfterRestoreCommit)=\"onRestored($event)\">\n * </mj-recycle-bin>\n *\n * @example Intercepting a restore\n * onBeforeRecordRestore(e: BeforeRecordRestoreEventArgs) {\n * if (!myCustomApproval(e.entry)) {\n * e.cancel = true;\n * e.cancelReason = 'Awaiting compliance approval';\n * }\n * }\n */\n@Component({\n standalone: false,\n selector: 'mj-recycle-bin',\n templateUrl: './recycle-bin.component.html',\n styleUrls: ['./recycle-bin.component.css'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n})\nexport class RecycleBinComponent extends BaseAngularComponent implements OnInit {\n // ─── Inputs ─────────────────────────────────────────────────────\n\n /**\n * Controls panel visibility. Set to true to slide in (and trigger an\n * initial load); false to slide out.\n */\n private _visible = false;\n @Input()\n set Visible(value: boolean) {\n const wasVisible = this._visible;\n this._visible = value;\n if (value && !wasVisible && this.isInitialized) {\n this.LoadDeletedRecords();\n }\n }\n get Visible(): boolean {\n return this._visible;\n }\n\n /**\n * The name of the entity whose deleted records will be listed. Required.\n */\n @Input() EntityName: string | null = null;\n\n /**\n * Optional context user. When omitted, falls back to\n * {@link this.ProviderToUse.CurrentUser} per standard MJ conventions.\n */\n @Input() ContextUser: UserInfo | null = null;\n\n /**\n * Maximum number of deleted-record cards to load. Defaults to 200 — this\n * is a UI affordance, not a hard limit; consumers needing pagination\n * should listen to {@link AfterRecycleBinOpen} and surface their own UI\n * if `deletedRecordCount === MaxRecords`.\n */\n @Input() MaxRecords = 200;\n\n // ─── Outputs ────────────────────────────────────────────────────\n\n /** Fires when the user closes the panel. */\n @Output() Closed = new EventEmitter<void>();\n\n /**\n * Cancelable. Fires when {@link Visible} flips to true and the bin is\n * about to query for deleted records. Setting `cancel = true` aborts\n * the query (and the panel will show the empty state).\n */\n @Output() BeforeRecycleBinOpen = new EventEmitter<BeforeRecycleBinOpenEventArgs>();\n\n /** Fires after the deleted-record query completes. */\n @Output() AfterRecycleBinOpen = new EventEmitter<AfterRecycleBinOpenEventArgs>();\n\n /**\n * Cancelable. Fires when the user clicks Restore on a deleted-record\n * card, before the preview panel opens. Setting `cancel = true` skips\n * the preview entirely — useful for consumers that want to take over\n * the restore flow themselves.\n */\n @Output() BeforeRecordRestore = new EventEmitter<BeforeRecordRestoreEventArgs>();\n\n /**\n * Fires after the user closes the restore preview, with `success`\n * indicating whether the actual insert worked.\n */\n @Output() AfterRecordRestore = new EventEmitter<AfterRecordRestoreEventArgs>();\n\n /**\n * Cancelable. Fires after the user clicks Restore in the preview but\n * before the entity is inserted. Setting `cancel = true` aborts the\n * insert.\n */\n @Output() BeforeRestoreCommit = new EventEmitter<BeforeRestoreCommitEventArgs>();\n\n /** Fires after the insert completes (success or failure). */\n @Output() AfterRestoreCommit = new EventEmitter<AfterRestoreCommitEventArgs>();\n\n // ─── Public template state ──────────────────────────────────────\n\n public IsLoading = false;\n public LoadError: string | null = null;\n public Entries: RecycleBinEntry[] = [];\n\n /** The currently selected entry whose preview is open. */\n public SelectedEntry: RecycleBinEntry | null = null;\n public PreviewVisible = false;\n\n /** Permission flags (computed once when EntityName is set). */\n public CanDelete = false;\n public CanCreate = false;\n\n private isInitialized = false;\n private resolvedEntityInfo: EntityInfo | null = null;\n\n constructor(private cdr: ChangeDetectorRef) {\n super();}\n\n ngOnInit(): void {\n this.isInitialized = true;\n if (this.Visible) this.LoadDeletedRecords();\n }\n\n // ─── Derived state ──────────────────────────────────────────────\n\n public get HeaderSubtitle(): string {\n if (!this.EntityName) return '';\n const n = this.Entries.length;\n return `${n} record${n === 1 ? '' : 's'} have been deleted from ${this.EntityName}.`;\n }\n\n /**\n * The user has Delete permission and may browse the bin. The Restore\n * button on each card additionally requires {@link CanCreate}.\n */\n public get HasAccess(): boolean {\n return this.CanDelete;\n }\n\n // ─── Public actions ─────────────────────────────────────────────\n\n /**\n * Manually trigger a re-load — useful for consumers who want to refresh\n * after some external event (e.g., a delete happened and they want the\n * bin to re-populate immediately).\n */\n public async LoadDeletedRecords(): Promise<void> {\n if (!this.EntityName) {\n this.LoadError = 'EntityName is required';\n return;\n }\n\n // Resolve metadata + permissions\n const md = this.ProviderToUse;\n const entityInfo = md.Entities.find(\n e => e.Name.trim().toLowerCase() === this.EntityName!.trim().toLowerCase(),\n );\n if (!entityInfo) {\n this.LoadError = `Entity '${this.EntityName}' not found`;\n this.cdr.markForCheck();\n return;\n }\n this.resolvedEntityInfo = entityInfo;\n const effectiveUser = this.ContextUser ?? md.CurrentUser;\n if (effectiveUser) {\n const perms = entityInfo.GetUserPermisions(effectiveUser);\n this.CanDelete = perms?.CanDelete ?? false;\n this.CanCreate = perms?.CanCreate ?? false;\n } else {\n this.CanDelete = false;\n this.CanCreate = false;\n }\n\n if (!this.CanDelete) {\n this.Entries = [];\n this.LoadError = 'You do not have Delete permission for this entity.';\n this.cdr.markForCheck();\n return;\n }\n\n // Fire BeforeRecycleBinOpen — consumer may cancel\n const beforeArgs: BeforeRecycleBinOpenEventArgs = {\n bin: this,\n timestamp: new Date(),\n entityName: this.EntityName,\n cancel: false,\n _kind: 'beforeRecycleBinOpen',\n };\n this.BeforeRecycleBinOpen.emit(beforeArgs);\n if (beforeArgs.cancel) {\n this.LoadError = beforeArgs.cancelReason ?? 'Cancelled by host';\n this.cdr.markForCheck();\n return;\n }\n\n this.IsLoading = true;\n this.LoadError = null;\n this.Entries = [];\n this.cdr.markForCheck();\n\n try {\n const rv = RunView.FromMetadataProvider(this.ProviderToUse);\n // Get the most recent Delete change per RecordID. We over-fetch and\n // dedupe in JS — the data is small and SQL grouping with the latest\n // change per ID is awkward across dialects.\n const result = await rv.RunView<MJRecordChangeEntity>(\n {\n EntityName: 'MJ: Record Changes',\n ExtraFilter: `EntityID='${entityInfo.ID}' AND Type='Delete'`,\n OrderBy: 'ChangedAt DESC',\n MaxRows: this.MaxRecords * 2, // dedupe headroom\n ResultType: 'entity_object',\n },\n this.ContextUser ?? undefined,\n );\n\n if (!result.Success) {\n this.LoadError = result.ErrorMessage || 'Failed to load deleted records';\n } else {\n this.Entries = this.dedupeAndBuildEntries(result.Results, entityInfo);\n }\n } catch (e) {\n this.LoadError = e instanceof Error ? e.message : 'Unknown error';\n } finally {\n this.IsLoading = false;\n this.cdr.markForCheck();\n }\n\n // Fire AfterRecycleBinOpen\n this.AfterRecycleBinOpen.emit({\n bin: this,\n timestamp: new Date(),\n entityName: this.EntityName,\n deletedRecordCount: this.Entries.length,\n _kind: 'afterRecycleBinOpen',\n });\n }\n\n /**\n * User clicked Restore on a card — open the preview panel (unless a\n * BeforeRecordRestore handler cancelled).\n */\n public OnRestoreClicked(entry: RecycleBinEntry): void {\n if (!this.CanCreate) return; // gated in template too\n\n const beforeArgs: BeforeRecordRestoreEventArgs = {\n bin: this,\n timestamp: new Date(),\n entityName: this.EntityName ?? '',\n entry,\n cancel: false,\n _kind: 'beforeRecordRestore',\n };\n this.BeforeRecordRestore.emit(beforeArgs);\n if (beforeArgs.cancel) return;\n\n this.SelectedEntry = entry;\n this.PreviewVisible = true;\n this.cdr.markForCheck();\n }\n\n /**\n * Preview panel emitted RestoreConfirmed — perform the insert and emit\n * the matching after* events.\n */\n public async OnPreviewConfirmed(commit: RestoreCommitEvent): Promise<void> {\n if (!this.SelectedEntry || !this.resolvedEntityInfo) return;\n const entry = this.SelectedEntry;\n\n const beforeArgs: BeforeRestoreCommitEventArgs = {\n bin: this,\n timestamp: new Date(),\n entityName: this.EntityName ?? '',\n entry,\n fieldValues: commit.FieldValues,\n reason: commit.Reason,\n cancel: false,\n _kind: 'beforeRestoreCommit',\n };\n this.BeforeRestoreCommit.emit(beforeArgs);\n if (beforeArgs.cancel) {\n this.PreviewVisible = false;\n this.cdr.markForCheck();\n return;\n }\n\n let success = false;\n let errorMessage: string | undefined;\n let newRecordID: string | undefined;\n\n try {\n const md = this.ProviderToUse;\n const entity = await md.GetEntityObject(this.resolvedEntityInfo.Name, this.ContextUser ?? undefined);\n\n // Apply the snapshot fields, including the original primary key\n // (preserves any FK references that still point at this ID).\n for (const fv of commit.FieldValues) {\n entity.Set(fv.FieldName, fv.Value);\n }\n // Also re-apply the original primary key from the snapshot so dangling\n // FK references continue to work. Set() on a fresh entity treats this\n // as the new record's PK.\n try {\n const snapshotData = JSON.parse(entry.RecordChange.FullRecordJSON || '{}');\n for (const pk of this.resolvedEntityInfo.PrimaryKeys) {\n if (snapshotData[pk.Name] != null) {\n entity.Set(pk.Name, snapshotData[pk.Name]);\n }\n }\n } catch {\n // PK preservation is best-effort\n }\n\n entity.SetRestoreContext(commit.SourceChangeID, commit.Reason);\n\n try {\n success = await entity.Save();\n if (success) {\n newRecordID = entity.PrimaryKey.ToString();\n } else {\n errorMessage = entity.LatestResult?.CompleteMessage ?? 'Save returned false';\n }\n } finally {\n entity.ClearRestoreContext();\n }\n } catch (e) {\n success = false;\n errorMessage = e instanceof Error ? e.message : 'Unknown error';\n }\n\n this.AfterRestoreCommit.emit({\n bin: this,\n timestamp: new Date(),\n entityName: this.EntityName ?? '',\n entry,\n success,\n newRecordID,\n errorMessage,\n _kind: 'afterRestoreCommit',\n });\n\n this.AfterRecordRestore.emit({\n bin: this,\n timestamp: new Date(),\n entityName: this.EntityName ?? '',\n entry,\n success,\n errorMessage,\n _kind: 'afterRecordRestore',\n });\n\n if (success) {\n // Remove the restored entry from the list and close preview\n this.Entries = this.Entries.filter(e => e !== entry);\n }\n this.PreviewVisible = false;\n this.SelectedEntry = null;\n this.cdr.markForCheck();\n }\n\n public OnPreviewCancelled(): void {\n this.PreviewVisible = false;\n this.SelectedEntry = null;\n this.cdr.markForCheck();\n }\n\n public OnClose(): void {\n this._visible = false;\n this.cdr.markForCheck();\n this.Closed.emit();\n }\n\n // ─── Internal: build entries from raw RecordChange rows ─────────\n\n /**\n * Dedupes the raw delete-change rows: keeps only the most recent Delete\n * per RecordID (since a record could in principle have been recreated\n * and re-deleted). Builds the display fields heuristically by reading\n * the snapshot JSON.\n */\n private dedupeAndBuildEntries(\n rows: MJRecordChangeEntity[],\n entityInfo: EntityInfo,\n ): RecycleBinEntry[] {\n const seen = new Set<string>();\n const entries: RecycleBinEntry[] = [];\n\n for (const row of rows) {\n if (entries.length >= this.MaxRecords) break;\n if (seen.has(row.RecordID)) continue;\n seen.add(row.RecordID);\n\n const snapshot = this.parseSnapshot(row.FullRecordJSON);\n if (!snapshot) continue;\n\n const entry: RecycleBinEntry = {\n RecordChange: row,\n RecordID: row.RecordID,\n DisplaySummary: this.buildDisplaySummary(snapshot, entityInfo),\n SupportingFields: this.buildSupportingFields(snapshot, entityInfo),\n };\n entries.push(entry);\n }\n\n return entries;\n }\n\n /**\n * Builds the headline of the card. Prefers the entity's name field, then\n * any non-PK string field, then the RecordID itself as a last resort.\n */\n private buildDisplaySummary(snapshot: Record<string, unknown>, entityInfo: EntityInfo): string {\n const nameField = entityInfo.NameField;\n if (nameField && snapshot[nameField.Name] != null && snapshot[nameField.Name] !== '') {\n return String(snapshot[nameField.Name]);\n }\n // Fallback: first non-PK, non-empty string field\n for (const f of entityInfo.Fields) {\n if (f.IsPrimaryKey) continue;\n const v = snapshot[f.Name];\n if (typeof v === 'string' && v.trim().length > 0) return v;\n }\n return `Record ${snapshot[entityInfo.PrimaryKeys[0]?.Name] ?? ''}`;\n }\n\n /**\n * Picks up to 3 \"interesting\" fields from the snapshot to render as\n * supporting metadata under the headline. Heuristic: prefer fields that\n * are non-empty, non-PK, non-system, and have a display name.\n */\n private buildSupportingFields(\n snapshot: Record<string, unknown>,\n entityInfo: EntityInfo,\n ): Array<{ Name: string; DisplayName: string; Value: string }> {\n const out: Array<{ Name: string; DisplayName: string; Value: string }> = [];\n const nameFieldName = entityInfo.NameField?.Name;\n\n for (const f of entityInfo.Fields) {\n if (out.length >= 3) break;\n if (f.IsPrimaryKey) continue;\n if (f.Name.startsWith('__mj_')) continue;\n if (f.Name === nameFieldName) continue;\n\n const v = snapshot[f.Name];\n if (v == null || v === '') continue;\n\n out.push({\n Name: f.Name,\n DisplayName: f.DisplayNameOrName,\n Value: this.formatFieldValue(v, f),\n });\n }\n return out;\n }\n\n private formatFieldValue(value: unknown, _field: EntityFieldInfo): string {\n if (value == null) return '';\n if (typeof value === 'object') {\n try {\n return JSON.stringify(value);\n } catch {\n return String(value);\n }\n }\n return String(value);\n }\n\n private parseSnapshot(json: string | null | undefined): Record<string, unknown> | null {\n if (!json) return null;\n try {\n return JSON.parse(json) as Record<string, unknown>;\n } catch {\n return null;\n }\n }\n\n // ─── Display helpers (template) ─────────────────────────────────\n\n public formatTimestamp(date: Date | string | null): string {\n if (!date) return '';\n return new Intl.DateTimeFormat('en-US', {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n hour: 'numeric',\n minute: '2-digit',\n hour12: true,\n }).format(new Date(date));\n }\n\n public getUserDisplay(user: string | null): string {\n if (!user) return 'Unknown';\n if (user.includes('@')) return user.split('@')[0];\n return user;\n }\n}\n","<mj-slide-panel\n [Mode]=\"'slide'\"\n [Title]=\"'Recycle Bin · ' + (EntityName || '')\"\n [Visible]=\"Visible\"\n [Resizable]=\"true\"\n [MinWidthPx]=\"480\"\n [MaxWidthRatio]=\"0.6\"\n (Closed)=\"OnClose()\">\n\n <div class=\"rb-container\">\n\n <!-- Header -->\n <div class=\"rb-header\">\n <div class=\"rb-header-icon\">\n <i class=\"fa-solid fa-trash-can-arrow-up\" aria-hidden=\"true\"></i>\n </div>\n <div class=\"rb-header-text\">\n <div class=\"rb-header-title\">Recycle Bin</div>\n @if (EntityName) {\n <div class=\"rb-header-subtitle\">{{ HeaderSubtitle }}</div>\n }\n </div>\n </div>\n\n <!-- No-permission state -->\n @if (!IsLoading && LoadError && !HasAccess) {\n <mj-empty-state\n Variant=\"warning\"\n Icon=\"fa-solid fa-lock\"\n Title=\"Access denied\"\n [Message]=\"LoadError\" />\n }\n\n <!-- Loading -->\n @if (IsLoading) {\n <mj-loading text=\"Loading deleted records...\" size=\"medium\"></mj-loading>\n }\n\n <!-- Generic error -->\n @if (!IsLoading && LoadError && HasAccess) {\n <mj-empty-state\n Variant=\"error\"\n Icon=\"fa-solid fa-triangle-exclamation\"\n Title=\"Could not load deleted records\"\n [Message]=\"LoadError\" />\n }\n\n <!-- Empty state -->\n @if (!IsLoading && !LoadError && HasAccess && Entries.length === 0) {\n <mj-empty-state\n Icon=\"fa-solid fa-trash-can\"\n Title=\"Recycle Bin is empty\"\n Message=\"No records have been deleted from this entity, or change tracking isn't enabled here.\" />\n }\n\n <!-- Entries -->\n @if (!IsLoading && !LoadError && Entries.length > 0) {\n <div class=\"rb-list\">\n @for (entry of Entries; track entry.RecordChange.ID) {\n <div class=\"rb-card\">\n <div class=\"rb-card-info\">\n <div class=\"rb-card-name\">{{ entry.DisplaySummary }}</div>\n @if (entry.SupportingFields.length > 0) {\n <div class=\"rb-card-fields\">\n @for (sf of entry.SupportingFields; track sf.Name) {\n <span class=\"rb-card-field\">\n <strong>{{ sf.DisplayName }}:</strong> {{ sf.Value }}\n </span>\n }\n </div>\n }\n <div class=\"rb-card-meta\">\n <i class=\"fa-solid fa-trash\" aria-hidden=\"true\"></i>\n Deleted {{ formatTimestamp(entry.RecordChange.ChangedAt) }}\n @if (entry.RecordChange.User) {\n by {{ getUserDisplay(entry.RecordChange.User) }}\n }\n </div>\n </div>\n <div class=\"rb-card-actions\">\n <button class=\"rb-btn rb-btn-restore\"\n [disabled]=\"!CanCreate\"\n [title]=\"CanCreate ? 'Re-create this record from its snapshot' : 'You do not have Create permission for this entity'\"\n (click)=\"OnRestoreClicked(entry)\"\n type=\"button\">\n <i class=\"fa-solid fa-rotate-left\" aria-hidden=\"true\"></i>\n Restore\n </button>\n </div>\n </div>\n }\n </div>\n }\n\n </div>\n\n <!-- Restore preview slide-in (reused from ng-record-changes) -->\n <mj-restore-preview-panel\n [Visible]=\"PreviewVisible\"\n [Mode]=\"'undelete'\"\n [RecordChange]=\"SelectedEntry?.RecordChange ?? null\"\n [EntityName]=\"EntityName\"\n (RestoreConfirmed)=\"OnPreviewConfirmed($event)\"\n (RestoreCancelled)=\"OnPreviewCancelled()\">\n </mj-restore-preview-panel>\n\n</mj-slide-panel>\n"]}
1
+ {"version":3,"file":"recycle-bin.component.js","sourceRoot":"","sources":["../../../src/lib/recycle-bin/recycle-bin.component.ts","../../../src/lib/recycle-bin/recycle-bin.component.html"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,uBAAuB,EAEvB,YAAY,EACZ,KAAK,EAEL,MAAM,EACN,iBAAiB,GAClB,MAAM,eAAe,CAAC;AACvB,OAAO,EAIL,OAAO,GAER,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;;;;;;;;ICC3D,8BAAgC;IAAA,YAAoB;IAAA,iBAAM;;;IAA1B,cAAoB;IAApB,2CAAoB;;;IAOxD,oCAI0B;;;IAAxB,0CAAqB;;;IAKvB,gCAAyE;;;IAKzE,qCAI0B;;;IAAxB,0CAAqB;;;IAKvB,qCAGoG;;;IAcpF,AADF,gCAA4B,aAClB;IAAA,YAAqB;IAAA,iBAAS;IAAC,YACzC;IAAA,iBAAO;;;IADG,eAAqB;IAArB,iDAAqB;IAAU,cACzC;IADyC,4CACzC;;;IAJJ,+BAA4B;IAC1B,2HAIC;IACH,iBAAM;;;IALJ,cAIC;IAJD,wCAIC;;;IAOD,YACF;;;;IADE,qFACF;;;;IAfF,AADF,AADF,+BAAqB,cACO,cACE;IAAA,YAA0B;IAAA,iBAAM;IAC1D,2GAAyC;IASzC,+BAA0B;IACxB,wBAAoD;IACpD,YACA;IAAA,gGAA+B;IAInC,AADE,iBAAM,EACF;IAEJ,AADF,+BAA6B,kBAKX;IADd,2NAAS,iCAAuB,KAAC;IAEjC,yBAA0D;IAC1D,0BACF;IAEJ,AADE,AADE,iBAAS,EACL,EACF;;;;IA5BwB,eAA0B;IAA1B,6CAA0B;IACpD,cAQC;IARD,+DAQC;IAGC,eACA;IADA,gGACA;IAAA,cAEC;IAFD,qDAEC;IAKD,eAAuB;IACvB,AADA,4CAAuB,6HAC8F;;;IAzB/H,+BAAqB;IACnB,uGAgCC;IACH,iBAAM;;;IAjCJ,cAgCC;IAhCD,6BAgCC;;AD5DT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AASH,MAAM,OAAO,mBAAoB,SAAQ,oBAAoB;IA+FvC;IA9FpB,mEAAmE;IAEnE;;;OAGG;IACK,QAAQ,GAAG,KAAK,CAAC;IACzB,IACI,OAAO,CAAC,KAAc;QACxB,MAAM,UAAU,GAAG,IAAI,CAAC,QAAQ,CAAC;QACjC,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;QACtB,IAAI,KAAK,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YAC/C,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC5B,CAAC;IACH,CAAC;IACD,IAAI,OAAO;QACT,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;IAED;;OAEG;IACM,UAAU,GAAkB,IAAI,CAAC;IAE1C;;;OAGG;IACM,WAAW,GAAoB,IAAI,CAAC;IAE7C;;;;;OAKG;IACM,UAAU,GAAG,GAAG,CAAC;IAE1B,mEAAmE;IAEnE,4CAA4C;IAClC,MAAM,GAAG,IAAI,YAAY,EAAQ,CAAC;IAE5C;;;;OAIG;IACO,oBAAoB,GAAG,IAAI,YAAY,EAAiC,CAAC;IAEnF,sDAAsD;IAC5C,mBAAmB,GAAG,IAAI,YAAY,EAAgC,CAAC;IAEjF;;;;;OAKG;IACO,mBAAmB,GAAG,IAAI,YAAY,EAAgC,CAAC;IAEjF;;;OAGG;IACO,kBAAkB,GAAG,IAAI,YAAY,EAA+B,CAAC;IAE/E;;;;OAIG;IACO,mBAAmB,GAAG,IAAI,YAAY,EAAgC,CAAC;IAEjF,6DAA6D;IACnD,kBAAkB,GAAG,IAAI,YAAY,EAA+B,CAAC;IAE/E,mEAAmE;IAE5D,SAAS,GAAG,KAAK,CAAC;IAClB,SAAS,GAAkB,IAAI,CAAC;IAChC,OAAO,GAAsB,EAAE,CAAC;IAEvC,0DAA0D;IACnD,aAAa,GAA2B,IAAI,CAAC;IAC7C,cAAc,GAAG,KAAK,CAAC;IAE9B,+DAA+D;IACxD,SAAS,GAAG,KAAK,CAAC;IAClB,SAAS,GAAG,KAAK,CAAC;IAEjB,aAAa,GAAG,KAAK,CAAC;IACtB,kBAAkB,GAAsB,IAAI,CAAC;IAErD,YAAoB,GAAsB;QAC1C,KAAK,EAAE,CAAC;QADY,QAAG,GAAH,GAAG,CAAmB;IAClC,CAAC;IAET,QAAQ;QACN,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;QAC1B,IAAI,IAAI,CAAC,OAAO;YAAE,IAAI,CAAC,kBAAkB,EAAE,CAAC;IAC9C,CAAC;IAED,mEAAmE;IAEnE,IAAW,cAAc;QACvB,IAAI,CAAC,IAAI,CAAC,UAAU;YAAE,OAAO,EAAE,CAAC;QAChC,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC;QAC9B,OAAO,GAAG,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,2BAA2B,IAAI,CAAC,UAAU,GAAG,CAAC;IACvF,CAAC;IAED;;;OAGG;IACH,IAAW,SAAS;QAClB,OAAO,IAAI,CAAC,SAAS,CAAC;IACxB,CAAC;IAED,mEAAmE;IAEnE;;;;OAIG;IACI,KAAK,CAAC,kBAAkB;QAC7B,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;YACrB,IAAI,CAAC,SAAS,GAAG,wBAAwB,CAAC;YAC1C,OAAO;QACT,CAAC;QAED,iCAAiC;QACjC,MAAM,EAAE,GAAG,IAAI,CAAC,aAAa,CAAC;QAC9B,MAAM,UAAU,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,CACjC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,KAAK,IAAI,CAAC,UAAW,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAC3E,CAAC;QACF,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,IAAI,CAAC,SAAS,GAAG,WAAW,IAAI,CAAC,UAAU,aAAa,CAAC;YACzD,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;YACxB,OAAO;QACT,CAAC;QACD,IAAI,CAAC,kBAAkB,GAAG,UAAU,CAAC;QACrC,MAAM,aAAa,GAAG,IAAI,CAAC,WAAW,IAAI,EAAE,CAAC,WAAW,CAAC;QACzD,IAAI,aAAa,EAAE,CAAC;YAClB,MAAM,KAAK,GAAG,UAAU,CAAC,iBAAiB,CAAC,aAAa,CAAC,CAAC;YAC1D,IAAI,CAAC,SAAS,GAAG,KAAK,EAAE,SAAS,IAAI,KAAK,CAAC;YAC3C,IAAI,CAAC,SAAS,GAAG,KAAK,EAAE,SAAS,IAAI,KAAK,CAAC;QAC7C,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;YACvB,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;QACzB,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YACpB,IAAI,CAAC,OAAO,GAAG,EAAE,CAAC;YAClB,IAAI,CAAC,SAAS,GAAG,oDAAoD,CAAC;YACtE,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;YACxB,OAAO;QACT,CAAC;QAED,kDAAkD;QAClD,MAAM,UAAU,GAAkC;YAChD,GAAG,EAAE,IAAI;YACT,SAAS,EAAE,IAAI,IAAI,EAAE;YACrB,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,MAAM,EAAE,KAAK;YACb,KAAK,EAAE,sBAAsB;SAC9B,CAAC;QACF,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC3C,IAAI,UAAU,CAAC,MAAM,EAAE,CAAC;YACtB,IAAI,CAAC,SAAS,GAAG,UAAU,CAAC,YAAY,IAAI,mBAAmB,CAAC;YAChE,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;YACxB,OAAO;QACT,CAAC;QAED,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QACtB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QACtB,IAAI,CAAC,OAAO,GAAG,EAAE,CAAC;QAClB,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;QAExB,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,OAAO,CAAC,oBAAoB,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;YAC5D,oEAAoE;YACpE,oEAAoE;YACpE,4CAA4C;YAC5C,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,OAAO,CAC7B;gBACE,UAAU,EAAE,oBAAoB;gBAChC,WAAW,EAAE,aAAa,UAAU,CAAC,EAAE,qBAAqB;gBAC5D,OAAO,EAAE,gBAAgB;gBACzB,OAAO,EAAE,IAAI,CAAC,UAAU,GAAG,CAAC,EAAE,kBAAkB;gBAChD,UAAU,EAAE,eAAe;aAC5B,EACD,IAAI,CAAC,WAAW,IAAI,SAAS,CAC9B,CAAC;YAEF,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;gBACpB,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC,YAAY,IAAI,gCAAgC,CAAC;YAC3E,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,qBAAqB,CAAC,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;YACxE,CAAC;QACH,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,IAAI,CAAC,SAAS,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC;QACpE,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;YACvB,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;QAC1B,CAAC;QAED,2BAA2B;QAC3B,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC;YAC5B,GAAG,EAAE,IAAI;YACT,SAAS,EAAE,IAAI,IAAI,EAAE;YACrB,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,kBAAkB,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM;YACvC,KAAK,EAAE,qBAAqB;SAC7B,CAAC,CAAC;IACL,CAAC;IAED;;;OAGG;IACI,gBAAgB,CAAC,KAAsB;QAC5C,IAAI,CAAC,IAAI,CAAC,SAAS;YAAE,OAAO,CAAC,wBAAwB;QAErD,MAAM,UAAU,GAAiC;YAC/C,GAAG,EAAE,IAAI;YACT,SAAS,EAAE,IAAI,IAAI,EAAE;YACrB,UAAU,EAAE,IAAI,CAAC,UAAU,IAAI,EAAE;YACjC,KAAK;YACL,MAAM,EAAE,KAAK;YACb,KAAK,EAAE,qBAAqB;SAC7B,CAAC;QACF,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC1C,IAAI,UAAU,CAAC,MAAM;YAAE,OAAO;QAE9B,IAAI,CAAC,aAAa,GAAG,KAAK,CAAC;QAC3B,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;QAC3B,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;IAC1B,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,kBAAkB,CAAC,MAA0B;QACxD,IAAI,CAAC,IAAI,CAAC,aAAa,IAAI,CAAC,IAAI,CAAC,kBAAkB;YAAE,OAAO;QAC5D,MAAM,KAAK,GAAG,IAAI,CAAC,aAAa,CAAC;QAEjC,MAAM,UAAU,GAAiC;YAC/C,GAAG,EAAE,IAAI;YACT,SAAS,EAAE,IAAI,IAAI,EAAE;YACrB,UAAU,EAAE,IAAI,CAAC,UAAU,IAAI,EAAE;YACjC,KAAK;YACL,WAAW,EAAE,MAAM,CAAC,WAAW;YAC/B,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,MAAM,EAAE,KAAK;YACb,KAAK,EAAE,qBAAqB;SAC7B,CAAC;QACF,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC1C,IAAI,UAAU,CAAC,MAAM,EAAE,CAAC;YACtB,IAAI,CAAC,cAAc,GAAG,KAAK,CAAC;YAC5B,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;YACxB,OAAO;QACT,CAAC;QAED,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,IAAI,YAAgC,CAAC;QACrC,IAAI,WAA+B,CAAC;QAEpC,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,IAAI,CAAC,aAAa,CAAC;YAC9B,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,kBAAkB,CAAC,IAAI,EAAE,IAAI,CAAC,WAAW,IAAI,SAAS,CAAC,CAAC;YAErG,gEAAgE;YAChE,6DAA6D;YAC7D,KAAK,MAAM,EAAE,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;gBACpC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,SAAS,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC;YACrC,CAAC;YACD,uEAAuE;YACvE,sEAAsE;YACtE,0BAA0B;YAC1B,IAAI,CAAC;gBACH,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,YAAY,CAAC,cAAc,IAAI,IAAI,CAAC,CAAC;gBAC3E,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,kBAAkB,CAAC,WAAW,EAAE,CAAC;oBACrD,IAAI,YAAY,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC;wBAClC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;oBAC7C,CAAC;gBACH,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,iCAAiC;YACnC,CAAC;YAED,MAAM,CAAC,iBAAiB,CAAC,MAAM,CAAC,cAAc,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;YAE/D,IAAI,CAAC;gBACH,OAAO,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;gBAC9B,IAAI,OAAO,EAAE,CAAC;oBACZ,WAAW,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,EAAE,CAAC;gBAC7C,CAAC;qBAAM,CAAC;oBACN,YAAY,GAAG,MAAM,CAAC,YAAY,EAAE,eAAe,IAAI,qBAAqB,CAAC;gBAC/E,CAAC;YACH,CAAC;oBAAS,CAAC;gBACT,MAAM,CAAC,mBAAmB,EAAE,CAAC;YAC/B,CAAC;QACH,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,GAAG,KAAK,CAAC;YAChB,YAAY,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC;QAClE,CAAC;QAED,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC;YAC3B,GAAG,EAAE,IAAI;YACT,SAAS,EAAE,IAAI,IAAI,EAAE;YACrB,UAAU,EAAE,IAAI,CAAC,UAAU,IAAI,EAAE;YACjC,KAAK;YACL,OAAO;YACP,WAAW;YACX,YAAY;YACZ,KAAK,EAAE,oBAAoB;SAC5B,CAAC,CAAC;QAEH,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC;YAC3B,GAAG,EAAE,IAAI;YACT,SAAS,EAAE,IAAI,IAAI,EAAE;YACrB,UAAU,EAAE,IAAI,CAAC,UAAU,IAAI,EAAE;YACjC,KAAK;YACL,OAAO;YACP,YAAY;YACZ,KAAK,EAAE,oBAAoB;SAC5B,CAAC,CAAC;QAEH,IAAI,OAAO,EAAE,CAAC;YACZ,4DAA4D;YAC5D,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC;QACvD,CAAC;QACD,IAAI,CAAC,cAAc,GAAG,KAAK,CAAC;QAC5B,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;QAC1B,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;IAC1B,CAAC;IAEM,kBAAkB;QACvB,IAAI,CAAC,cAAc,GAAG,KAAK,CAAC;QAC5B,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;QAC1B,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;IAC1B,CAAC;IAEM,OAAO;QACZ,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;QACtB,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;QACxB,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;IACrB,CAAC;IAED,mEAAmE;IAEnE;;;;;OAKG;IACK,qBAAqB,CAC3B,IAA4B,EAC5B,UAAsB;QAEtB,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;QAC/B,MAAM,OAAO,GAAsB,EAAE,CAAC;QAEtC,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,IAAI,OAAO,CAAC,MAAM,IAAI,IAAI,CAAC,UAAU;gBAAE,MAAM;YAC7C,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC;gBAAE,SAAS;YACrC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YAEvB,MAAM,QAAQ,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;YACxD,IAAI,CAAC,QAAQ;gBAAE,SAAS;YAExB,MAAM,KAAK,GAAoB;gBAC7B,YAAY,EAAE,GAAG;gBACjB,QAAQ,EAAE,GAAG,CAAC,QAAQ;gBACtB,cAAc,EAAE,IAAI,CAAC,mBAAmB,CAAC,QAAQ,EAAE,UAAU,CAAC;gBAC9D,gBAAgB,EAAE,IAAI,CAAC,qBAAqB,CAAC,QAAQ,EAAE,UAAU,CAAC;aACnE,CAAC;YACF,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC;QAED,OAAO,OAAO,CAAC;IACjB,CAAC;IAED;;;OAGG;IACK,mBAAmB,CAAC,QAAiC,EAAE,UAAsB;QACnF,MAAM,SAAS,GAAG,UAAU,CAAC,SAAS,CAAC;QACvC,IAAI,SAAS,IAAI,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC;YACrF,OAAO,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;QAC1C,CAAC;QACD,iDAAiD;QACjD,KAAK,MAAM,CAAC,IAAI,UAAU,CAAC,MAAM,EAAE,CAAC;YAClC,IAAI,CAAC,CAAC,YAAY;gBAAE,SAAS;YAC7B,MAAM,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YAC3B,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC;gBAAE,OAAO,CAAC,CAAC;QAC7D,CAAC;QACD,qGAAqG;QACrG,MAAM,SAAS,GAAG,UAAU,CAAC,WAAW;aACrC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;aAC9B,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;aACpC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QACzB,OAAO,UAAU,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;IAC1C,CAAC;IAED;;;;OAIG;IACK,qBAAqB,CAC3B,QAAiC,EACjC,UAAsB;QAEtB,MAAM,GAAG,GAAgE,EAAE,CAAC;QAC5E,MAAM,aAAa,GAAG,UAAU,CAAC,SAAS,EAAE,IAAI,CAAC;QAEjD,KAAK,MAAM,CAAC,IAAI,UAAU,CAAC,MAAM,EAAE,CAAC;YAClC,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC;gBAAE,MAAM;YAC3B,IAAI,CAAC,CAAC,YAAY;gBAAE,SAAS;YAC7B,IAAI,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;gBAAE,SAAS;YACzC,IAAI,CAAC,CAAC,IAAI,KAAK,aAAa;gBAAE,SAAS;YAEvC,MAAM,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YAC3B,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE;gBAAE,SAAS;YAEpC,GAAG,CAAC,IAAI,CAAC;gBACP,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,WAAW,EAAE,CAAC,CAAC,iBAAiB;gBAChC,KAAK,EAAE,IAAI,CAAC,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAC;aACnC,CAAC,CAAC;QACL,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAEO,gBAAgB,CAAC,KAAc,EAAE,MAAuB;QAC9D,IAAI,KAAK,IAAI,IAAI;YAAE,OAAO,EAAE,CAAC;QAC7B,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC9B,IAAI,CAAC;gBACH,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;YAC/B,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;YACvB,CAAC;QACH,CAAC;QACD,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;IAEO,aAAa,CAAC,IAA+B;QACnD,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAC;QACvB,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAA4B,CAAC;QACrD,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,mEAAmE;IAE5D,eAAe,CAAC,IAA0B;QAC/C,IAAI,CAAC,IAAI;YAAE,OAAO,EAAE,CAAC;QACrB,OAAO,IAAI,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE;YACtC,IAAI,EAAE,SAAS;YACf,KAAK,EAAE,OAAO;YACd,GAAG,EAAE,SAAS;YACd,IAAI,EAAE,SAAS;YACf,MAAM,EAAE,SAAS;YACjB,MAAM,EAAE,IAAI;SACb,CAAC,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAC5B,CAAC;IAEM,cAAc,CAAC,IAAmB;QACvC,IAAI,CAAC,IAAI;YAAE,OAAO,SAAS,CAAC;QAC5B,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAClD,OAAO,IAAI,CAAC;IACd,CAAC;6GA/dU,mBAAmB;6DAAnB,mBAAmB;YC7FhC,yCAOuB;YAArB,0GAAU,aAAS,IAAC;YAMhB,AADF,AAHF,8BAA0B,aAGD,aACO;YAC1B,uBAAiE;YACnE,iBAAM;YAEJ,AADF,8BAA4B,aACG;YAAA,2BAAW;YAAA,iBAAM;YAC9C,qFAAkB;YAItB,AADE,iBAAM,EACF;YAGN,gGAA6C;YAS7C,8FAAiB;YAKjB,mGAA4C;YAS5C,mGAAqE;YAQrE,wFAAsD;YAsCxD,iBAAM;YAGN,qDAM4C;YAA1C,AADA,+IAAoB,8BAA0B,IAAC,4HAC3B,wBAAoB,IAAC;YAG7C,AAFE,iBAA2B,EAEZ;;YApGf,AADA,AADA,AADA,AADA,AADA,8BAAgB,yDAC+B,wBAC5B,mBACD,mBACA,sBACG;YAYf,eAEC;YAFD,yCAEC;YAKL,cAMC;YAND,4EAMC;YAGD,cAEC;YAFD,yCAEC;YAGD,cAMC;YAND,4EAMC;YAGD,cAKC;YALD,yGAKC;YAGD,cAoCC;YApCD,sFAoCC;YAMD,cAA0B;YAG1B,AADA,AADA,AADA,4CAA0B,oBACP,6FACiC,8BAC3B;;;iFDRhB,mBAAmB;cAR/B,SAAS;6BACI,KAAK,YACP,gBAAgB,mBAGT,uBAAuB,CAAC,MAAM,iBAChC,iBAAiB,CAAC,IAAI;;kBAUpC,KAAK;;kBAeL,KAAK;;kBAML,KAAK;;kBAQL,KAAK;;kBAKL,MAAM;;kBAON,MAAM;;kBAGN,MAAM;;kBAQN,MAAM;;kBAMN,MAAM;;kBAON,MAAM;;kBAGN,MAAM;;kFA5EI,mBAAmB","sourcesContent":["import {\n Component,\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n EventEmitter,\n Input,\n OnInit,\n Output,\n ViewEncapsulation,\n} from '@angular/core';\nimport {\n EntityFieldInfo,\n EntityInfo,\n Metadata,\n RunView,\n UserInfo,\n} from '@memberjunction/core';\nimport { MJRecordChangeEntity } from '@memberjunction/core-entities';\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\nimport { RestoreCommitEvent } from '@memberjunction/ng-record-changes';\nimport {\n AfterRecordRestoreEventArgs,\n AfterRecycleBinOpenEventArgs,\n AfterRestoreCommitEventArgs,\n BeforeRecordRestoreEventArgs,\n BeforeRecycleBinOpenEventArgs,\n BeforeRestoreCommitEventArgs,\n RecycleBinEntry,\n} from './events/recycle-bin-events';\n\n/**\n * Slide-in panel that lists all hard-deleted records for a single entity\n * and lets a user with `Delete` permission re-create any of them from its\n * historical RecordChange snapshot.\n *\n * ### When to use\n *\n * Surface this component from any entity-viewing context where the user\n * might need to restore a hard-deleted record. The {@link EntityViewerComponent},\n * {@link EntityDataGridComponent}, and {@link EntityCardsComponent}\n * already expose a `ShowRecycleBin` input (default `true`) that renders a\n * chip in their toolbar — you only need to use this component directly if\n * you're building a custom viewer.\n *\n * ### Permissions\n *\n * The chip / panel is gated on `entity.UserPermissions.CanDelete` —\n * rationale: there is no native \"undelete\" permission in MemberJunction,\n * but if a user has the higher-trust permission to *delete* records of an\n * entity, restoring deleted ones is well within scope. The actual\n * re-create action additionally requires `CanCreate`; without it the\n * Restore button on each card disables with a tooltip.\n *\n * ### Soft deletes vs hard deletes\n *\n * This component only surfaces *hard*-deleted records. Soft-deletes (e.g.,\n * `IsDeleted` flags, `Status='Inactive'`, etc.) leave the record visible\n * in normal entity views, so the standard Record Changes panel + restore\n * preview already handles them — no Recycle Bin needed.\n *\n * ### Cancelable Before/After event surface\n *\n * Every meaningful action emits a paired `before*` / `after*` event. The\n * `before*` event carries `cancel: boolean` so consumers can intercept\n * — useful for custom approval workflows, audit logging, or to take over\n * the actual restore execution entirely. See {@link BeforeRecordRestoreEventArgs}\n * and friends.\n *\n * @example Basic usage (rare — usually embedded by entity-viewer)\n * <mj-recycle-bin\n * [Visible]=\"showBin\"\n * [EntityName]=\"'Customers'\"\n * (Closed)=\"showBin = false\"\n * (BeforeRecordRestore)=\"auditRestore($event)\"\n * (AfterRestoreCommit)=\"onRestored($event)\">\n * </mj-recycle-bin>\n *\n * @example Intercepting a restore\n * onBeforeRecordRestore(e: BeforeRecordRestoreEventArgs) {\n * if (!myCustomApproval(e.entry)) {\n * e.cancel = true;\n * e.cancelReason = 'Awaiting compliance approval';\n * }\n * }\n */\n@Component({\n standalone: false,\n selector: 'mj-recycle-bin',\n templateUrl: './recycle-bin.component.html',\n styleUrls: ['./recycle-bin.component.css'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n})\nexport class RecycleBinComponent extends BaseAngularComponent implements OnInit {\n // ─── Inputs ─────────────────────────────────────────────────────\n\n /**\n * Controls panel visibility. Set to true to slide in (and trigger an\n * initial load); false to slide out.\n */\n private _visible = false;\n @Input()\n set Visible(value: boolean) {\n const wasVisible = this._visible;\n this._visible = value;\n if (value && !wasVisible && this.isInitialized) {\n this.LoadDeletedRecords();\n }\n }\n get Visible(): boolean {\n return this._visible;\n }\n\n /**\n * The name of the entity whose deleted records will be listed. Required.\n */\n @Input() EntityName: string | null = null;\n\n /**\n * Optional context user. When omitted, falls back to\n * {@link this.ProviderToUse.CurrentUser} per standard MJ conventions.\n */\n @Input() ContextUser: UserInfo | null = null;\n\n /**\n * Maximum number of deleted-record cards to load. Defaults to 200 — this\n * is a UI affordance, not a hard limit; consumers needing pagination\n * should listen to {@link AfterRecycleBinOpen} and surface their own UI\n * if `deletedRecordCount === MaxRecords`.\n */\n @Input() MaxRecords = 200;\n\n // ─── Outputs ────────────────────────────────────────────────────\n\n /** Fires when the user closes the panel. */\n @Output() Closed = new EventEmitter<void>();\n\n /**\n * Cancelable. Fires when {@link Visible} flips to true and the bin is\n * about to query for deleted records. Setting `cancel = true` aborts\n * the query (and the panel will show the empty state).\n */\n @Output() BeforeRecycleBinOpen = new EventEmitter<BeforeRecycleBinOpenEventArgs>();\n\n /** Fires after the deleted-record query completes. */\n @Output() AfterRecycleBinOpen = new EventEmitter<AfterRecycleBinOpenEventArgs>();\n\n /**\n * Cancelable. Fires when the user clicks Restore on a deleted-record\n * card, before the preview panel opens. Setting `cancel = true` skips\n * the preview entirely — useful for consumers that want to take over\n * the restore flow themselves.\n */\n @Output() BeforeRecordRestore = new EventEmitter<BeforeRecordRestoreEventArgs>();\n\n /**\n * Fires after the user closes the restore preview, with `success`\n * indicating whether the actual insert worked.\n */\n @Output() AfterRecordRestore = new EventEmitter<AfterRecordRestoreEventArgs>();\n\n /**\n * Cancelable. Fires after the user clicks Restore in the preview but\n * before the entity is inserted. Setting `cancel = true` aborts the\n * insert.\n */\n @Output() BeforeRestoreCommit = new EventEmitter<BeforeRestoreCommitEventArgs>();\n\n /** Fires after the insert completes (success or failure). */\n @Output() AfterRestoreCommit = new EventEmitter<AfterRestoreCommitEventArgs>();\n\n // ─── Public template state ──────────────────────────────────────\n\n public IsLoading = false;\n public LoadError: string | null = null;\n public Entries: RecycleBinEntry[] = [];\n\n /** The currently selected entry whose preview is open. */\n public SelectedEntry: RecycleBinEntry | null = null;\n public PreviewVisible = false;\n\n /** Permission flags (computed once when EntityName is set). */\n public CanDelete = false;\n public CanCreate = false;\n\n private isInitialized = false;\n private resolvedEntityInfo: EntityInfo | null = null;\n\n constructor(private cdr: ChangeDetectorRef) {\n super();}\n\n ngOnInit(): void {\n this.isInitialized = true;\n if (this.Visible) this.LoadDeletedRecords();\n }\n\n // ─── Derived state ──────────────────────────────────────────────\n\n public get HeaderSubtitle(): string {\n if (!this.EntityName) return '';\n const n = this.Entries.length;\n return `${n} record${n === 1 ? '' : 's'} have been deleted from ${this.EntityName}.`;\n }\n\n /**\n * The user has Delete permission and may browse the bin. The Restore\n * button on each card additionally requires {@link CanCreate}.\n */\n public get HasAccess(): boolean {\n return this.CanDelete;\n }\n\n // ─── Public actions ─────────────────────────────────────────────\n\n /**\n * Manually trigger a re-load — useful for consumers who want to refresh\n * after some external event (e.g., a delete happened and they want the\n * bin to re-populate immediately).\n */\n public async LoadDeletedRecords(): Promise<void> {\n if (!this.EntityName) {\n this.LoadError = 'EntityName is required';\n return;\n }\n\n // Resolve metadata + permissions\n const md = this.ProviderToUse;\n const entityInfo = md.Entities.find(\n e => e.Name.trim().toLowerCase() === this.EntityName!.trim().toLowerCase(),\n );\n if (!entityInfo) {\n this.LoadError = `Entity '${this.EntityName}' not found`;\n this.cdr.markForCheck();\n return;\n }\n this.resolvedEntityInfo = entityInfo;\n const effectiveUser = this.ContextUser ?? md.CurrentUser;\n if (effectiveUser) {\n const perms = entityInfo.GetUserPermisions(effectiveUser);\n this.CanDelete = perms?.CanDelete ?? false;\n this.CanCreate = perms?.CanCreate ?? false;\n } else {\n this.CanDelete = false;\n this.CanCreate = false;\n }\n\n if (!this.CanDelete) {\n this.Entries = [];\n this.LoadError = 'You do not have Delete permission for this entity.';\n this.cdr.markForCheck();\n return;\n }\n\n // Fire BeforeRecycleBinOpen — consumer may cancel\n const beforeArgs: BeforeRecycleBinOpenEventArgs = {\n bin: this,\n timestamp: new Date(),\n entityName: this.EntityName,\n cancel: false,\n _kind: 'beforeRecycleBinOpen',\n };\n this.BeforeRecycleBinOpen.emit(beforeArgs);\n if (beforeArgs.cancel) {\n this.LoadError = beforeArgs.cancelReason ?? 'Cancelled by host';\n this.cdr.markForCheck();\n return;\n }\n\n this.IsLoading = true;\n this.LoadError = null;\n this.Entries = [];\n this.cdr.markForCheck();\n\n try {\n const rv = RunView.FromMetadataProvider(this.ProviderToUse);\n // Get the most recent Delete change per RecordID. We over-fetch and\n // dedupe in JS — the data is small and SQL grouping with the latest\n // change per ID is awkward across dialects.\n const result = await rv.RunView<MJRecordChangeEntity>(\n {\n EntityName: 'MJ: Record Changes',\n ExtraFilter: `EntityID='${entityInfo.ID}' AND Type='Delete'`,\n OrderBy: 'ChangedAt DESC',\n MaxRows: this.MaxRecords * 2, // dedupe headroom\n ResultType: 'entity_object',\n },\n this.ContextUser ?? undefined,\n );\n\n if (!result.Success) {\n this.LoadError = result.ErrorMessage || 'Failed to load deleted records';\n } else {\n this.Entries = this.dedupeAndBuildEntries(result.Results, entityInfo);\n }\n } catch (e) {\n this.LoadError = e instanceof Error ? e.message : 'Unknown error';\n } finally {\n this.IsLoading = false;\n this.cdr.markForCheck();\n }\n\n // Fire AfterRecycleBinOpen\n this.AfterRecycleBinOpen.emit({\n bin: this,\n timestamp: new Date(),\n entityName: this.EntityName,\n deletedRecordCount: this.Entries.length,\n _kind: 'afterRecycleBinOpen',\n });\n }\n\n /**\n * User clicked Restore on a card — open the preview panel (unless a\n * BeforeRecordRestore handler cancelled).\n */\n public OnRestoreClicked(entry: RecycleBinEntry): void {\n if (!this.CanCreate) return; // gated in template too\n\n const beforeArgs: BeforeRecordRestoreEventArgs = {\n bin: this,\n timestamp: new Date(),\n entityName: this.EntityName ?? '',\n entry,\n cancel: false,\n _kind: 'beforeRecordRestore',\n };\n this.BeforeRecordRestore.emit(beforeArgs);\n if (beforeArgs.cancel) return;\n\n this.SelectedEntry = entry;\n this.PreviewVisible = true;\n this.cdr.markForCheck();\n }\n\n /**\n * Preview panel emitted RestoreConfirmed — perform the insert and emit\n * the matching after* events.\n */\n public async OnPreviewConfirmed(commit: RestoreCommitEvent): Promise<void> {\n if (!this.SelectedEntry || !this.resolvedEntityInfo) return;\n const entry = this.SelectedEntry;\n\n const beforeArgs: BeforeRestoreCommitEventArgs = {\n bin: this,\n timestamp: new Date(),\n entityName: this.EntityName ?? '',\n entry,\n fieldValues: commit.FieldValues,\n reason: commit.Reason,\n cancel: false,\n _kind: 'beforeRestoreCommit',\n };\n this.BeforeRestoreCommit.emit(beforeArgs);\n if (beforeArgs.cancel) {\n this.PreviewVisible = false;\n this.cdr.markForCheck();\n return;\n }\n\n let success = false;\n let errorMessage: string | undefined;\n let newRecordID: string | undefined;\n\n try {\n const md = this.ProviderToUse;\n const entity = await md.GetEntityObject(this.resolvedEntityInfo.Name, this.ContextUser ?? undefined);\n\n // Apply the snapshot fields, including the original primary key\n // (preserves any FK references that still point at this ID).\n for (const fv of commit.FieldValues) {\n entity.Set(fv.FieldName, fv.Value);\n }\n // Also re-apply the original primary key from the snapshot so dangling\n // FK references continue to work. Set() on a fresh entity treats this\n // as the new record's PK.\n try {\n const snapshotData = JSON.parse(entry.RecordChange.FullRecordJSON || '{}');\n for (const pk of this.resolvedEntityInfo.PrimaryKeys) {\n if (snapshotData[pk.Name] != null) {\n entity.Set(pk.Name, snapshotData[pk.Name]);\n }\n }\n } catch {\n // PK preservation is best-effort\n }\n\n entity.SetRestoreContext(commit.SourceChangeID, commit.Reason);\n\n try {\n success = await entity.Save();\n if (success) {\n newRecordID = entity.PrimaryKey.ToString();\n } else {\n errorMessage = entity.LatestResult?.CompleteMessage ?? 'Save returned false';\n }\n } finally {\n entity.ClearRestoreContext();\n }\n } catch (e) {\n success = false;\n errorMessage = e instanceof Error ? e.message : 'Unknown error';\n }\n\n this.AfterRestoreCommit.emit({\n bin: this,\n timestamp: new Date(),\n entityName: this.EntityName ?? '',\n entry,\n success,\n newRecordID,\n errorMessage,\n _kind: 'afterRestoreCommit',\n });\n\n this.AfterRecordRestore.emit({\n bin: this,\n timestamp: new Date(),\n entityName: this.EntityName ?? '',\n entry,\n success,\n errorMessage,\n _kind: 'afterRecordRestore',\n });\n\n if (success) {\n // Remove the restored entry from the list and close preview\n this.Entries = this.Entries.filter(e => e !== entry);\n }\n this.PreviewVisible = false;\n this.SelectedEntry = null;\n this.cdr.markForCheck();\n }\n\n public OnPreviewCancelled(): void {\n this.PreviewVisible = false;\n this.SelectedEntry = null;\n this.cdr.markForCheck();\n }\n\n public OnClose(): void {\n this._visible = false;\n this.cdr.markForCheck();\n this.Closed.emit();\n }\n\n // ─── Internal: build entries from raw RecordChange rows ─────────\n\n /**\n * Dedupes the raw delete-change rows: keeps only the most recent Delete\n * per RecordID (since a record could in principle have been recreated\n * and re-deleted). Builds the display fields heuristically by reading\n * the snapshot JSON.\n */\n private dedupeAndBuildEntries(\n rows: MJRecordChangeEntity[],\n entityInfo: EntityInfo,\n ): RecycleBinEntry[] {\n const seen = new Set<string>();\n const entries: RecycleBinEntry[] = [];\n\n for (const row of rows) {\n if (entries.length >= this.MaxRecords) break;\n if (seen.has(row.RecordID)) continue;\n seen.add(row.RecordID);\n\n const snapshot = this.parseSnapshot(row.FullRecordJSON);\n if (!snapshot) continue;\n\n const entry: RecycleBinEntry = {\n RecordChange: row,\n RecordID: row.RecordID,\n DisplaySummary: this.buildDisplaySummary(snapshot, entityInfo),\n SupportingFields: this.buildSupportingFields(snapshot, entityInfo),\n };\n entries.push(entry);\n }\n\n return entries;\n }\n\n /**\n * Builds the headline of the card. Prefers the entity's name field, then\n * any non-PK string field, then the RecordID itself as a last resort.\n */\n private buildDisplaySummary(snapshot: Record<string, unknown>, entityInfo: EntityInfo): string {\n const nameField = entityInfo.NameField;\n if (nameField && snapshot[nameField.Name] != null && snapshot[nameField.Name] !== '') {\n return String(snapshot[nameField.Name]);\n }\n // Fallback: first non-PK, non-empty string field\n for (const f of entityInfo.Fields) {\n if (f.IsPrimaryKey) continue;\n const v = snapshot[f.Name];\n if (typeof v === 'string' && v.trim().length > 0) return v;\n }\n // Last resort: every primary-key column's value (the entity is arbitrary and may be composite-keyed)\n const keyValues = entityInfo.PrimaryKeys\n .map((pk) => snapshot[pk.Name])\n .filter((v) => v != null && v !== '')\n .map((v) => String(v));\n return `Record ${keyValues.join(', ')}`;\n }\n\n /**\n * Picks up to 3 \"interesting\" fields from the snapshot to render as\n * supporting metadata under the headline. Heuristic: prefer fields that\n * are non-empty, non-PK, non-system, and have a display name.\n */\n private buildSupportingFields(\n snapshot: Record<string, unknown>,\n entityInfo: EntityInfo,\n ): Array<{ Name: string; DisplayName: string; Value: string }> {\n const out: Array<{ Name: string; DisplayName: string; Value: string }> = [];\n const nameFieldName = entityInfo.NameField?.Name;\n\n for (const f of entityInfo.Fields) {\n if (out.length >= 3) break;\n if (f.IsPrimaryKey) continue;\n if (f.Name.startsWith('__mj_')) continue;\n if (f.Name === nameFieldName) continue;\n\n const v = snapshot[f.Name];\n if (v == null || v === '') continue;\n\n out.push({\n Name: f.Name,\n DisplayName: f.DisplayNameOrName,\n Value: this.formatFieldValue(v, f),\n });\n }\n return out;\n }\n\n private formatFieldValue(value: unknown, _field: EntityFieldInfo): string {\n if (value == null) return '';\n if (typeof value === 'object') {\n try {\n return JSON.stringify(value);\n } catch {\n return String(value);\n }\n }\n return String(value);\n }\n\n private parseSnapshot(json: string | null | undefined): Record<string, unknown> | null {\n if (!json) return null;\n try {\n return JSON.parse(json) as Record<string, unknown>;\n } catch {\n return null;\n }\n }\n\n // ─── Display helpers (template) ─────────────────────────────────\n\n public formatTimestamp(date: Date | string | null): string {\n if (!date) return '';\n return new Intl.DateTimeFormat('en-US', {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n hour: 'numeric',\n minute: '2-digit',\n hour12: true,\n }).format(new Date(date));\n }\n\n public getUserDisplay(user: string | null): string {\n if (!user) return 'Unknown';\n if (user.includes('@')) return user.split('@')[0];\n return user;\n }\n}\n","<mj-slide-panel\n [Mode]=\"'slide'\"\n [Title]=\"'Recycle Bin · ' + (EntityName || '')\"\n [Visible]=\"Visible\"\n [Resizable]=\"true\"\n [MinWidthPx]=\"480\"\n [MaxWidthRatio]=\"0.6\"\n (Closed)=\"OnClose()\">\n\n <div class=\"rb-container\">\n\n <!-- Header -->\n <div class=\"rb-header\">\n <div class=\"rb-header-icon\">\n <i class=\"fa-solid fa-trash-can-arrow-up\" aria-hidden=\"true\"></i>\n </div>\n <div class=\"rb-header-text\">\n <div class=\"rb-header-title\">Recycle Bin</div>\n @if (EntityName) {\n <div class=\"rb-header-subtitle\">{{ HeaderSubtitle }}</div>\n }\n </div>\n </div>\n\n <!-- No-permission state -->\n @if (!IsLoading && LoadError && !HasAccess) {\n <mj-empty-state\n Variant=\"warning\"\n Icon=\"fa-solid fa-lock\"\n Title=\"Access denied\"\n [Message]=\"LoadError\" />\n }\n\n <!-- Loading -->\n @if (IsLoading) {\n <mj-loading text=\"Loading deleted records...\" size=\"medium\"></mj-loading>\n }\n\n <!-- Generic error -->\n @if (!IsLoading && LoadError && HasAccess) {\n <mj-empty-state\n Variant=\"error\"\n Icon=\"fa-solid fa-triangle-exclamation\"\n Title=\"Could not load deleted records\"\n [Message]=\"LoadError\" />\n }\n\n <!-- Empty state -->\n @if (!IsLoading && !LoadError && HasAccess && Entries.length === 0) {\n <mj-empty-state\n Icon=\"fa-solid fa-trash-can\"\n Title=\"Recycle Bin is empty\"\n Message=\"No records have been deleted from this entity, or change tracking isn't enabled here.\" />\n }\n\n <!-- Entries -->\n @if (!IsLoading && !LoadError && Entries.length > 0) {\n <div class=\"rb-list\">\n @for (entry of Entries; track entry.RecordChange.ID) {\n <div class=\"rb-card\">\n <div class=\"rb-card-info\">\n <div class=\"rb-card-name\">{{ entry.DisplaySummary }}</div>\n @if (entry.SupportingFields.length > 0) {\n <div class=\"rb-card-fields\">\n @for (sf of entry.SupportingFields; track sf.Name) {\n <span class=\"rb-card-field\">\n <strong>{{ sf.DisplayName }}:</strong> {{ sf.Value }}\n </span>\n }\n </div>\n }\n <div class=\"rb-card-meta\">\n <i class=\"fa-solid fa-trash\" aria-hidden=\"true\"></i>\n Deleted {{ formatTimestamp(entry.RecordChange.ChangedAt) }}\n @if (entry.RecordChange.User) {\n by {{ getUserDisplay(entry.RecordChange.User) }}\n }\n </div>\n </div>\n <div class=\"rb-card-actions\">\n <button class=\"rb-btn rb-btn-restore\"\n [disabled]=\"!CanCreate\"\n [title]=\"CanCreate ? 'Re-create this record from its snapshot' : 'You do not have Create permission for this entity'\"\n (click)=\"OnRestoreClicked(entry)\"\n type=\"button\">\n <i class=\"fa-solid fa-rotate-left\" aria-hidden=\"true\"></i>\n Restore\n </button>\n </div>\n </div>\n }\n </div>\n }\n\n </div>\n\n <!-- Restore preview slide-in (reused from ng-record-changes) -->\n <mj-restore-preview-panel\n [Visible]=\"PreviewVisible\"\n [Mode]=\"'undelete'\"\n [RecordChange]=\"SelectedEntry?.RecordChange ?? null\"\n [EntityName]=\"EntityName\"\n (RestoreConfirmed)=\"OnPreviewConfirmed($event)\"\n (RestoreCancelled)=\"OnPreviewCancelled()\">\n </mj-restore-preview-panel>\n\n</mj-slide-panel>\n"]}
@@ -81,7 +81,7 @@ export class SharedViewWarningDialogComponent {
81
81
  i0.ɵɵclassProp("open", ctx.IsOpen);
82
82
  i0.ɵɵadvance(11);
83
83
  i0.ɵɵtextInterpolate1("\"", ctx.ViewName, "\"");
84
- } }, dependencies: [i1.MJAlertComponent], styles: ["\n\n.dialog-backdrop[_ngcontent-%COMP%] {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.4);\n z-index: 2000;\n animation: _ngcontent-%COMP%_fadeIn 0.2s ease;\n}\n\n@keyframes _ngcontent-%COMP%_fadeIn {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n\n\n.dialog-panel[_ngcontent-%COMP%] {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%) scale(0.95);\n width: 440px;\n max-width: calc(100vw - 40px);\n background: var(--mj-bg-surface);\n border-radius: 16px;\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);\n z-index: 2001;\n display: flex;\n flex-direction: column;\n opacity: 0;\n pointer-events: none;\n transition: opacity 0.2s ease, transform 0.2s ease;\n}\n\n.dialog-panel.open[_ngcontent-%COMP%] {\n opacity: 1;\n pointer-events: auto;\n transform: translate(-50%, -50%) scale(1);\n}\n\n\n\n.dialog-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 20px 24px 0;\n}\n\n.icon-circle[_ngcontent-%COMP%] {\n width: 40px;\n height: 40px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 18px;\n flex-shrink: 0;\n}\n\n.icon-circle.warning[_ngcontent-%COMP%] {\n background: var(--mj-status-warning-bg);\n color: var(--mj-status-warning);\n}\n\n.header-title[_ngcontent-%COMP%] {\n font-size: 18px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.close-btn[_ngcontent-%COMP%] {\n width: 32px;\n height: 32px;\n border: none;\n background: transparent;\n border-radius: 8px;\n cursor: pointer;\n color: var(--mj-text-muted);\n transition: all 0.15s ease;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-left: auto;\n}\n\n.close-btn[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-primary);\n}\n\n\n\n.dialog-body[_ngcontent-%COMP%] {\n padding: 20px 24px;\n}\n\n.message[_ngcontent-%COMP%] {\n font-size: 14px;\n color: var(--mj-text-muted);\n line-height: 1.6;\n margin: 0 0 16px 0;\n}\n\n.message[_ngcontent-%COMP%] strong[_ngcontent-%COMP%] {\n color: var(--mj-text-primary);\n}\n\n\n\n\n.shared-view-alert-pos[_ngcontent-%COMP%] { margin: 0; }\n\n\n\n.dialog-footer[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 16px 24px 20px;\n border-top: 1px solid var(--mj-border-default);\n}\n\n\n\n.btn[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 9px 16px;\n border: none;\n border-radius: 8px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n white-space: nowrap;\n}\n\n.btn-primary[_ngcontent-%COMP%] {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n}\n\n.btn-primary[_ngcontent-%COMP%]:hover {\n background: var(--mj-color-info-600);\n}\n\n.btn-secondary[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n border: 1px solid var(--mj-border-strong);\n}\n\n.btn-secondary[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-card);\n border-color: var(--mj-text-disabled);\n}\n\n.btn-ghost[_ngcontent-%COMP%] {\n background: transparent;\n color: var(--mj-text-muted);\n margin-left: auto;\n}\n\n.btn-ghost[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-primary);\n}"] });
84
+ } }, dependencies: [i1.MJAlertComponent], styles: ["\n.dialog-backdrop[_ngcontent-%COMP%] {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.4);\n z-index: 2000;\n animation: _ngcontent-%COMP%_fadeIn 0.2s ease;\n}\n\n@keyframes _ngcontent-%COMP%_fadeIn {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n\n.dialog-panel[_ngcontent-%COMP%] {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%) scale(0.95);\n width: 440px;\n max-width: calc(100vw - 40px);\n background: var(--mj-bg-surface);\n border-radius: 16px;\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);\n z-index: 2001;\n display: flex;\n flex-direction: column;\n opacity: 0;\n pointer-events: none;\n transition: opacity 0.2s ease, transform 0.2s ease;\n}\n\n.dialog-panel.open[_ngcontent-%COMP%] {\n opacity: 1;\n pointer-events: auto;\n transform: translate(-50%, -50%) scale(1);\n}\n\n\n.dialog-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 20px 24px 0;\n}\n\n.icon-circle[_ngcontent-%COMP%] {\n width: 40px;\n height: 40px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 18px;\n flex-shrink: 0;\n}\n\n.icon-circle.warning[_ngcontent-%COMP%] {\n background: var(--mj-status-warning-bg);\n color: var(--mj-status-warning);\n}\n\n.header-title[_ngcontent-%COMP%] {\n font-size: 18px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.close-btn[_ngcontent-%COMP%] {\n width: 32px;\n height: 32px;\n border: none;\n background: transparent;\n border-radius: 8px;\n cursor: pointer;\n color: var(--mj-text-muted);\n transition: all 0.15s ease;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-left: auto;\n}\n\n.close-btn[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-primary);\n}\n\n\n.dialog-body[_ngcontent-%COMP%] {\n padding: 20px 24px;\n}\n\n.message[_ngcontent-%COMP%] {\n font-size: 14px;\n color: var(--mj-text-muted);\n line-height: 1.6;\n margin: 0 0 16px 0;\n}\n\n.message[_ngcontent-%COMP%] strong[_ngcontent-%COMP%] {\n color: var(--mj-text-primary);\n}\n\n\n\n.shared-view-alert-pos[_ngcontent-%COMP%] { margin: 0; }\n\n\n.dialog-footer[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 16px 24px 20px;\n border-top: 1px solid var(--mj-border-default);\n}\n\n\n.btn[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 9px 16px;\n border: none;\n border-radius: 8px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n white-space: nowrap;\n}\n\n.btn-primary[_ngcontent-%COMP%] {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n}\n\n.btn-primary[_ngcontent-%COMP%]:hover {\n background: var(--mj-color-info-600);\n}\n\n.btn-secondary[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n border: 1px solid var(--mj-border-strong);\n}\n\n.btn-secondary[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-card);\n border-color: var(--mj-text-disabled);\n}\n\n.btn-ghost[_ngcontent-%COMP%] {\n background: transparent;\n color: var(--mj-text-muted);\n margin-left: auto;\n}\n\n.btn-ghost[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-primary);\n}"] });
85
85
  }
86
86
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(SharedViewWarningDialogComponent, [{
87
87
  type: Component,
@@ -186,10 +186,24 @@ export interface GridStateChangedEvent {
186
186
  /** What changed: 'columns', 'sort', 'filter' */
187
187
  changeType: 'columns' | 'sort' | 'filter';
188
188
  }
189
+ /**
190
+ * Chrome density for {@link EntityViewerConfig}.
191
+ *
192
+ * - `workspace` — Explorer record page: filter, view-mode toggle, count, pagination, grid toolbar.
193
+ * - `embedded` — dashboard / peek card: none of that chrome. Grid toolbar and pager are seeded off
194
+ * as well so hosts do not have to know Grid's opaque view-type blob.
195
+ */
196
+ export type EntityViewerChrome = 'workspace' | 'embedded';
189
197
  /**
190
198
  * Configuration options for the EntityViewer component
191
199
  */
192
200
  export interface EntityViewerConfig {
201
+ /**
202
+ * Chrome density. `embedded` fills in compact defaults (no filter, no view-mode toggle,
203
+ * no record count, no pagination, pageSize 8). Explicit booleans still win.
204
+ * @default 'workspace'
205
+ */
206
+ chrome?: EntityViewerChrome;
193
207
  /**
194
208
  * Whether to show the filter input box
195
209
  * @default true
@@ -384,4 +398,11 @@ export interface ViewSaveResult {
384
398
  IsNew: boolean;
385
399
  }
386
400
  export declare const DEFAULT_VIEWER_CONFIG: Required<EntityViewerConfig>;
401
+ /** Compact defaults applied when {@link EntityViewerConfig.chrome} is `embedded`. */
402
+ export declare const EMBEDDED_VIEWER_CHROME: Partial<EntityViewerConfig>;
403
+ /**
404
+ * Merge viewer config: workspace defaults, then embedded preset when requested, then explicit fields.
405
+ * Explicit booleans always win over the chrome preset.
406
+ */
407
+ export declare function ResolveViewerConfig(partial?: Partial<EntityViewerConfig>): Required<EntityViewerConfig>;
387
408
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/lib/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/lib/types.ts"],"names":[],"mappings":"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"}
package/dist/lib/types.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { ViewGridState, } from '@memberjunction/core-entities';
2
2
  export { ViewGridState };
3
3
  export const DEFAULT_VIEWER_CONFIG = {
4
+ chrome: 'workspace',
4
5
  showFilter: true,
5
6
  showViewModeToggle: true,
6
7
  selectionBehavior: 'emit-only',
@@ -26,4 +27,26 @@ export const DEFAULT_VIEWER_CONFIG = {
26
27
  defaultSortField: '',
27
28
  defaultSortDirection: 'asc'
28
29
  };
30
+ /** Compact defaults applied when {@link EntityViewerConfig.chrome} is `embedded`. */
31
+ export const EMBEDDED_VIEWER_CHROME = {
32
+ showFilter: false,
33
+ showViewModeToggle: false,
34
+ showRecordCount: false,
35
+ showPagination: false,
36
+ pageSize: 8,
37
+ };
38
+ /**
39
+ * Merge viewer config: workspace defaults, then embedded preset when requested, then explicit fields.
40
+ * Explicit booleans always win over the chrome preset.
41
+ */
42
+ export function ResolveViewerConfig(partial = {}) {
43
+ const chrome = partial.chrome ?? 'workspace';
44
+ const preset = chrome === 'embedded' ? EMBEDDED_VIEWER_CHROME : {};
45
+ return {
46
+ ...DEFAULT_VIEWER_CONFIG,
47
+ ...preset,
48
+ ...partial,
49
+ chrome,
50
+ };
51
+ }
29
52
  //# sourceMappingURL=types.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/lib/types.ts"],"names":[],"mappings":"AACA,OAAO,EACL,aAAa,GAMd,MAAM,+BAA+B,CAAC;AAMvC,OAAO,EAAE,aAAa,EAAE,CAAC;AA6azB,MAAM,CAAC,MAAM,qBAAqB,GAAiC;IACjE,UAAU,EAAE,IAAI;IAChB,kBAAkB,EAAE,IAAI;IACxB,iBAAiB,EAAE,WAAW;IAC9B,eAAe,EAAE,MAAM;IACvB,iBAAiB,EAAE,KAAK;IACxB,QAAQ,EAAE,GAAG;IACb,eAAe,EAAE,IAAI;IACrB,iBAAiB,EAAE,mBAAmB;IACtC,gBAAgB,EAAE,GAAG;IACrB,WAAW,EAAE,EAAE;IACf,YAAY,EAAE;QACZ,WAAW,EAAE,EAAE;QACf,aAAa,EAAE,IAAI;QACnB,gBAAgB,EAAE,IAAI;QACtB,aAAa,EAAE,EAAE;QACjB,eAAe,EAAE,EAAE;QACnB,UAAU,EAAE,IAAI;KACjB;IACD,MAAM,EAAE,MAAM;IACd,mBAAmB,EAAE,IAAI;IACzB,iBAAiB,EAAE,IAAI;IACvB,cAAc,EAAE,IAAI;IACpB,gBAAgB,EAAE,EAAE;IACpB,oBAAoB,EAAE,KAAK;CAC5B,CAAC","sourcesContent":["import { EntityInfo, CompositeKey } from '@memberjunction/core';\nimport {\n ViewGridState,\n MJUserViewEntity_IGridSortSetting,\n MJUserViewEntity_IGridColumnSetting,\n MJUserViewEntity_ITimelineState,\n MJUserViewEntity_IDisplayCardState,\n MJUserViewEntity_IGridDisplayState,\n} from '@memberjunction/core-entities';\n\n// Re-export core types for backward compatibility\nexport type ViewColumnPinned = 'left' | 'right' | null;\nexport type ViewGridSortSetting = MJUserViewEntity_IGridSortSetting;\nexport type ViewGridColumnSetting = MJUserViewEntity_IGridColumnSetting;\nexport { ViewGridState };\n\n/**\n * View modes supported by the EntityViewer component\n */\nexport type EntityViewMode = 'grid' | 'cards' | 'timeline' | 'map';\n\n/**\n * Behavior when a record is selected\n */\nexport type RecordSelectionBehavior =\n | 'emit-only' // Only emit the recordSelected event\n | 'show-detail' // Show the detail panel (if available)\n | 'emit-and-detail'; // Both emit event and show detail panel\n\n/**\n * Field display type for smart rendering in cards\n */\nexport type CardFieldType = 'number' | 'boolean' | 'text' | 'date';\n\n/**\n * Color categories for status pills based on semantic meaning\n */\nexport type PillColorType = 'success' | 'warning' | 'danger' | 'info' | 'neutral';\n\n/**\n * Metadata for a field to display in a card\n */\nexport interface CardDisplayField {\n /** Field name from the entity */\n name: string;\n /** Display type for rendering */\n type: CardFieldType;\n /** Human-readable label */\n label: string;\n}\n\n/**\n * Auto-generated card template based on entity metadata\n */\nexport interface CardTemplate {\n /**\n * One or more field names that together form the card title.\n * Multiple IsNameField fields are combined with spaces\n * (e.g., [\"FirstName\", \"LastName\"] → \"Elizabeth Rodriguez\").\n */\n titleFields: string[];\n /** Secondary subtitle field name */\n subtitleField: string | null;\n /** Description/notes field name */\n descriptionField: string | null;\n /** Display fields with type information for smart rendering */\n displayFields: CardDisplayField[];\n /**\n * Array of thumbnail field names in priority order\n * Per-record fallback: if the first field is empty, try the next, etc.\n */\n thumbnailFields: string[];\n /** Badge/priority field name */\n badgeField: string | null;\n}\n\n/**\n * Column definition for the grid view\n */\nexport interface GridColumnDef {\n /** Field name from the entity */\n field: string;\n /** Column header text */\n headerName: string;\n /** Column width in pixels */\n width?: number;\n /** Minimum column width */\n minWidth?: number;\n /** Maximum column width */\n maxWidth?: number;\n /** Whether column is sortable */\n sortable?: boolean;\n /** Whether column is filterable */\n filter?: boolean;\n /** Whether column is resizable */\n resizable?: boolean;\n /** Whether to hide this column */\n hide?: boolean;\n /** Custom cell renderer type */\n cellRenderer?: string;\n /** Value formatter function name */\n valueFormatter?: string;\n}\n\n/**\n * Event emitted when a record is selected (clicked)\n */\nexport interface RecordSelectedEvent {\n /** The selected record (plain object from ResultType: 'simple') */\n record: Record<string, unknown>;\n /** The entity metadata */\n entity: EntityInfo;\n /** The composite key of the record */\n compositeKey: CompositeKey;\n}\n\n/**\n * Event emitted when a record should be opened (double-click or open button)\n */\nexport interface RecordOpenedEvent {\n /** The record to open (may be undefined for FK navigation where record isn't loaded) */\n record?: Record<string, unknown>;\n /** The entity metadata */\n entity: EntityInfo;\n /** The composite key of the record */\n compositeKey: CompositeKey;\n}\n\n/**\n * Event emitted when data is loaded\n */\nexport interface DataLoadedEvent {\n /** Total number of records available */\n totalRowCount: number;\n /** Number of records currently loaded */\n loadedRowCount: number;\n /** Time taken to load in milliseconds */\n loadTime: number;\n /** The loaded records - allows parent to access records for state restoration */\n records: Record<string, unknown>[];\n}\n\n/**\n * Event emitted when filtered count changes\n */\nexport interface FilteredCountChangedEvent {\n /** Number of records after filtering */\n filteredCount: number;\n /** Total number of records before filtering */\n totalCount: number;\n}\n\n/**\n * Sort direction for server-side sorting\n */\nexport type SortDirection = 'asc' | 'desc' | null;\n\n/**\n * Sort state for a column\n */\nexport interface SortState {\n /** Field name to sort by */\n field: string;\n /** Sort direction */\n direction: SortDirection;\n}\n\n/**\n * Event emitted when sort changes in the grid\n */\nexport interface SortChangedEvent {\n /** The new sort state (null if no sorting) */\n sort: SortState | null;\n}\n\n/**\n * Pagination state for server-side paging\n */\nexport interface PaginationState {\n /** Current page number (0-based) */\n currentPage: number;\n /** Number of records per page */\n pageSize: number;\n /** Total number of records available (from server) */\n totalRecords: number;\n /** Whether there are more records to load */\n hasMore: boolean;\n /** Whether data is currently being loaded */\n isLoading: boolean;\n}\n\n/**\n * Event emitted when pagination changes\n */\nexport interface PaginationChangedEvent {\n /** The new pagination state */\n pagination: PaginationState;\n}\n\n/**\n * Event emitted when requesting to load more data\n */\nexport interface LoadMoreEvent {\n /** Current page being requested (0-based) */\n page: number;\n /** Page size */\n pageSize: number;\n}\n\n/**\n * Event emitted when grid state changes (column resize, reorder, etc.)\n */\nexport interface GridStateChangedEvent {\n /** The updated grid state */\n gridState: ViewGridState;\n /** What changed: 'columns', 'sort', 'filter' */\n changeType: 'columns' | 'sort' | 'filter';\n}\n\n/**\n * Configuration options for the EntityViewer component\n */\nexport interface EntityViewerConfig {\n /**\n * Whether to show the filter input box\n * @default true\n */\n showFilter?: boolean;\n\n /**\n * Whether to show the view mode toggle (grid/cards)\n * @default true\n */\n showViewModeToggle?: boolean;\n\n /**\n * Behavior when a record is selected\n * @default 'emit-only'\n */\n selectionBehavior?: RecordSelectionBehavior;\n\n /**\n * Initial view mode\n * @default 'grid'\n */\n defaultViewMode?: EntityViewMode;\n\n /**\n * Whether to enable multi-select\n * @default false\n */\n enableMultiSelect?: boolean;\n\n /**\n * Maximum number of records to load per page\n * @default 100\n */\n pageSize?: number;\n\n /**\n * Whether to show record count in header\n * @default true\n */\n showRecordCount?: boolean;\n\n /**\n * Placeholder text for the filter input\n * @default 'Filter records...'\n */\n filterPlaceholder?: string;\n\n /**\n * Debounce time for filter input in milliseconds\n * @default 250\n */\n filterDebounceMs?: number;\n\n /**\n * Custom grid column definitions (optional - auto-generated if not provided)\n */\n gridColumns?: GridColumnDef[];\n\n /**\n * Custom card template (optional - auto-generated if not provided)\n */\n cardTemplate?: CardTemplate;\n\n /**\n * Height of the component (CSS value)\n * @default '100%'\n */\n height?: string;\n\n /**\n * Whether to use server-side filtering via UserSearchString\n * When true, filter text is sent to the server for SQL-based filtering\n * When false, filtering is done client-side on loaded records\n * @default true\n */\n serverSideFiltering?: boolean;\n\n /**\n * Whether to use server-side sorting via OrderBy\n * When true, sort changes trigger a new server request\n * When false, sorting is done client-side by AG Grid\n * @default true\n */\n serverSideSorting?: boolean;\n\n /**\n * Whether to show pagination controls\n * @default true\n */\n showPagination?: boolean;\n\n /**\n * Default sort field when loading data\n */\n defaultSortField?: string;\n\n /**\n * Default sort direction when loading data\n * @default 'asc'\n */\n defaultSortDirection?: SortDirection;\n}\n\n/**\n * Timeline segment grouping options\n */\nexport type TimelineSegmentGrouping = 'none' | 'day' | 'week' | 'month' | 'quarter' | 'year';\n\n/**\n * Timeline orientation options\n */\nexport type TimelineOrientation = 'vertical' | 'horizontal';\n\n/**\n * Timeline-specific configuration state\n * Persisted in UserView.DisplayState JSON\n *\n * Extends the generated {@link MJUserViewEntity_ITimelineState} with Angular-specific fields.\n */\nexport type TimelineState = MJUserViewEntity_ITimelineState;\n\n/**\n * Card-specific configuration state\n * Persisted in UserView.DisplayState JSON\n *\n * Identical to the generated {@link MJUserViewEntity_IDisplayCardState}.\n */\nexport type CardState = MJUserViewEntity_IDisplayCardState;\n\n/**\n * Grid-specific configuration state\n * Persisted in UserView.DisplayState JSON\n * Note: Most grid state is already in GridState column, this is for additional settings\n *\n * Extends the generated {@link MJUserViewEntity_IGridDisplayState} with Angular-specific fields.\n */\nexport type GridDisplayState = MJUserViewEntity_IGridDisplayState;\n\n/**\n * View display state - persisted in UserView.DisplayState\n * Contains the default view mode and mode-specific configuration\n */\nexport interface ViewDisplayState {\n /** The default view mode to show when loading this view */\n defaultMode: EntityViewMode;\n /** Which view modes are enabled/visible for this view */\n enabledModes?: {\n grid?: boolean;\n cards?: boolean;\n timeline?: boolean;\n };\n /** Timeline-specific configuration */\n timeline?: TimelineState;\n /** Card-specific configuration */\n cards?: CardState;\n /** Grid-specific configuration */\n grid?: GridDisplayState;\n}\n\n/**\n * Default configuration values\n */\n/**\n * Summary of what a view configuration includes, for preview in Quick Save dialog\n */\nexport interface ViewConfigSummary {\n /** Number of visible columns */\n ColumnCount: number;\n /** Number of active filters */\n FilterCount: number;\n /** Number of sort levels */\n SortCount: number;\n /** Whether smart filter is active */\n SmartFilterActive: boolean;\n /** Smart filter prompt text (if active) */\n SmartFilterPrompt: string;\n /** Number of enabled aggregates */\n AggregateCount: number;\n}\n\n/**\n * Event emitted by QuickSaveDialog when user saves\n */\nexport interface QuickSaveEvent {\n /** View name */\n Name: string;\n /** View description (optional) */\n Description: string;\n /** Whether the view is shared */\n IsShared: boolean;\n /** Whether to save as new (true) or update existing (false) */\n SaveAsNew: boolean;\n}\n\n/**\n * Data emitted by QuickSaveDialog when user clicks \"Customize columns, filters & sorting...\"\n * Carries the partially-filled form data so the config panel can continue the new-view creation.\n */\nexport interface QuickSaveAdvancedEvent {\n /** View name entered so far */\n Name: string;\n /** View description entered so far */\n Description: string;\n /** Whether the view should be shared */\n IsShared: boolean;\n}\n\n/**\n * Result of a view save operation (for notification display)\n */\nexport interface ViewSaveResult {\n /** Whether the save succeeded */\n Success: boolean;\n /** Error message (on failure) */\n ErrorMessage?: string;\n /** Whether this was a new view or update */\n IsNew: boolean;\n}\n\nexport const DEFAULT_VIEWER_CONFIG: Required<EntityViewerConfig> = {\n showFilter: true,\n showViewModeToggle: true,\n selectionBehavior: 'emit-only',\n defaultViewMode: 'grid',\n enableMultiSelect: false,\n pageSize: 100,\n showRecordCount: true,\n filterPlaceholder: 'Filter records...',\n filterDebounceMs: 250,\n gridColumns: [],\n cardTemplate: {\n titleFields: [],\n subtitleField: null,\n descriptionField: null,\n displayFields: [],\n thumbnailFields: [],\n badgeField: null\n },\n height: '100%',\n serverSideFiltering: true,\n serverSideSorting: true,\n showPagination: true,\n defaultSortField: '',\n defaultSortDirection: 'asc'\n};\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/lib/types.ts"],"names":[],"mappings":"AACA,OAAO,EACL,aAAa,GAMd,MAAM,+BAA+B,CAAC;AAMvC,OAAO,EAAE,aAAa,EAAE,CAAC;AA6bzB,MAAM,CAAC,MAAM,qBAAqB,GAAiC;IACjE,MAAM,EAAE,WAAW;IACnB,UAAU,EAAE,IAAI;IAChB,kBAAkB,EAAE,IAAI;IACxB,iBAAiB,EAAE,WAAW;IAC9B,eAAe,EAAE,MAAM;IACvB,iBAAiB,EAAE,KAAK;IACxB,QAAQ,EAAE,GAAG;IACb,eAAe,EAAE,IAAI;IACrB,iBAAiB,EAAE,mBAAmB;IACtC,gBAAgB,EAAE,GAAG;IACrB,WAAW,EAAE,EAAE;IACf,YAAY,EAAE;QACZ,WAAW,EAAE,EAAE;QACf,aAAa,EAAE,IAAI;QACnB,gBAAgB,EAAE,IAAI;QACtB,aAAa,EAAE,EAAE;QACjB,eAAe,EAAE,EAAE;QACnB,UAAU,EAAE,IAAI;KACjB;IACD,MAAM,EAAE,MAAM;IACd,mBAAmB,EAAE,IAAI;IACzB,iBAAiB,EAAE,IAAI;IACvB,cAAc,EAAE,IAAI;IACpB,gBAAgB,EAAE,EAAE;IACpB,oBAAoB,EAAE,KAAK;CAC5B,CAAC;AAEF,qFAAqF;AACrF,MAAM,CAAC,MAAM,sBAAsB,GAAgC;IACjE,UAAU,EAAE,KAAK;IACjB,kBAAkB,EAAE,KAAK;IACzB,eAAe,EAAE,KAAK;IACtB,cAAc,EAAE,KAAK;IACrB,QAAQ,EAAE,CAAC;CACZ,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,UAAuC,EAAE;IAC3E,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,WAAW,CAAC;IAC7C,MAAM,MAAM,GAAG,MAAM,KAAK,UAAU,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,EAAE,CAAC;IACnE,OAAO;QACL,GAAG,qBAAqB;QACxB,GAAG,MAAM;QACT,GAAG,OAAO;QACV,MAAM;KACP,CAAC;AACJ,CAAC","sourcesContent":["import { EntityInfo, CompositeKey } from '@memberjunction/core';\nimport {\n ViewGridState,\n MJUserViewEntity_IGridSortSetting,\n MJUserViewEntity_IGridColumnSetting,\n MJUserViewEntity_ITimelineState,\n MJUserViewEntity_IDisplayCardState,\n MJUserViewEntity_IGridDisplayState,\n} from '@memberjunction/core-entities';\n\n// Re-export core types for backward compatibility\nexport type ViewColumnPinned = 'left' | 'right' | null;\nexport type ViewGridSortSetting = MJUserViewEntity_IGridSortSetting;\nexport type ViewGridColumnSetting = MJUserViewEntity_IGridColumnSetting;\nexport { ViewGridState };\n\n/**\n * View modes supported by the EntityViewer component\n */\nexport type EntityViewMode = 'grid' | 'cards' | 'timeline' | 'map';\n\n/**\n * Behavior when a record is selected\n */\nexport type RecordSelectionBehavior =\n | 'emit-only' // Only emit the recordSelected event\n | 'show-detail' // Show the detail panel (if available)\n | 'emit-and-detail'; // Both emit event and show detail panel\n\n/**\n * Field display type for smart rendering in cards\n */\nexport type CardFieldType = 'number' | 'boolean' | 'text' | 'date';\n\n/**\n * Color categories for status pills based on semantic meaning\n */\nexport type PillColorType = 'success' | 'warning' | 'danger' | 'info' | 'neutral';\n\n/**\n * Metadata for a field to display in a card\n */\nexport interface CardDisplayField {\n /** Field name from the entity */\n name: string;\n /** Display type for rendering */\n type: CardFieldType;\n /** Human-readable label */\n label: string;\n}\n\n/**\n * Auto-generated card template based on entity metadata\n */\nexport interface CardTemplate {\n /**\n * One or more field names that together form the card title.\n * Multiple IsNameField fields are combined with spaces\n * (e.g., [\"FirstName\", \"LastName\"] → \"Elizabeth Rodriguez\").\n */\n titleFields: string[];\n /** Secondary subtitle field name */\n subtitleField: string | null;\n /** Description/notes field name */\n descriptionField: string | null;\n /** Display fields with type information for smart rendering */\n displayFields: CardDisplayField[];\n /**\n * Array of thumbnail field names in priority order\n * Per-record fallback: if the first field is empty, try the next, etc.\n */\n thumbnailFields: string[];\n /** Badge/priority field name */\n badgeField: string | null;\n}\n\n/**\n * Column definition for the grid view\n */\nexport interface GridColumnDef {\n /** Field name from the entity */\n field: string;\n /** Column header text */\n headerName: string;\n /** Column width in pixels */\n width?: number;\n /** Minimum column width */\n minWidth?: number;\n /** Maximum column width */\n maxWidth?: number;\n /** Whether column is sortable */\n sortable?: boolean;\n /** Whether column is filterable */\n filter?: boolean;\n /** Whether column is resizable */\n resizable?: boolean;\n /** Whether to hide this column */\n hide?: boolean;\n /** Custom cell renderer type */\n cellRenderer?: string;\n /** Value formatter function name */\n valueFormatter?: string;\n}\n\n/**\n * Event emitted when a record is selected (clicked)\n */\nexport interface RecordSelectedEvent {\n /** The selected record (plain object from ResultType: 'simple') */\n record: Record<string, unknown>;\n /** The entity metadata */\n entity: EntityInfo;\n /** The composite key of the record */\n compositeKey: CompositeKey;\n}\n\n/**\n * Event emitted when a record should be opened (double-click or open button)\n */\nexport interface RecordOpenedEvent {\n /** The record to open (may be undefined for FK navigation where record isn't loaded) */\n record?: Record<string, unknown>;\n /** The entity metadata */\n entity: EntityInfo;\n /** The composite key of the record */\n compositeKey: CompositeKey;\n}\n\n/**\n * Event emitted when data is loaded\n */\nexport interface DataLoadedEvent {\n /** Total number of records available */\n totalRowCount: number;\n /** Number of records currently loaded */\n loadedRowCount: number;\n /** Time taken to load in milliseconds */\n loadTime: number;\n /** The loaded records - allows parent to access records for state restoration */\n records: Record<string, unknown>[];\n}\n\n/**\n * Event emitted when filtered count changes\n */\nexport interface FilteredCountChangedEvent {\n /** Number of records after filtering */\n filteredCount: number;\n /** Total number of records before filtering */\n totalCount: number;\n}\n\n/**\n * Sort direction for server-side sorting\n */\nexport type SortDirection = 'asc' | 'desc' | null;\n\n/**\n * Sort state for a column\n */\nexport interface SortState {\n /** Field name to sort by */\n field: string;\n /** Sort direction */\n direction: SortDirection;\n}\n\n/**\n * Event emitted when sort changes in the grid\n */\nexport interface SortChangedEvent {\n /** The new sort state (null if no sorting) */\n sort: SortState | null;\n}\n\n/**\n * Pagination state for server-side paging\n */\nexport interface PaginationState {\n /** Current page number (0-based) */\n currentPage: number;\n /** Number of records per page */\n pageSize: number;\n /** Total number of records available (from server) */\n totalRecords: number;\n /** Whether there are more records to load */\n hasMore: boolean;\n /** Whether data is currently being loaded */\n isLoading: boolean;\n}\n\n/**\n * Event emitted when pagination changes\n */\nexport interface PaginationChangedEvent {\n /** The new pagination state */\n pagination: PaginationState;\n}\n\n/**\n * Event emitted when requesting to load more data\n */\nexport interface LoadMoreEvent {\n /** Current page being requested (0-based) */\n page: number;\n /** Page size */\n pageSize: number;\n}\n\n/**\n * Event emitted when grid state changes (column resize, reorder, etc.)\n */\nexport interface GridStateChangedEvent {\n /** The updated grid state */\n gridState: ViewGridState;\n /** What changed: 'columns', 'sort', 'filter' */\n changeType: 'columns' | 'sort' | 'filter';\n}\n\n/**\n * Chrome density for {@link EntityViewerConfig}.\n *\n * - `workspace` — Explorer record page: filter, view-mode toggle, count, pagination, grid toolbar.\n * - `embedded` — dashboard / peek card: none of that chrome. Grid toolbar and pager are seeded off\n * as well so hosts do not have to know Grid's opaque view-type blob.\n */\nexport type EntityViewerChrome = 'workspace' | 'embedded';\n\n/**\n * Configuration options for the EntityViewer component\n */\nexport interface EntityViewerConfig {\n /**\n * Chrome density. `embedded` fills in compact defaults (no filter, no view-mode toggle,\n * no record count, no pagination, pageSize 8). Explicit booleans still win.\n * @default 'workspace'\n */\n chrome?: EntityViewerChrome;\n\n /**\n * Whether to show the filter input box\n * @default true\n */\n showFilter?: boolean;\n\n /**\n * Whether to show the view mode toggle (grid/cards)\n * @default true\n */\n showViewModeToggle?: boolean;\n\n /**\n * Behavior when a record is selected\n * @default 'emit-only'\n */\n selectionBehavior?: RecordSelectionBehavior;\n\n /**\n * Initial view mode\n * @default 'grid'\n */\n defaultViewMode?: EntityViewMode;\n\n /**\n * Whether to enable multi-select\n * @default false\n */\n enableMultiSelect?: boolean;\n\n /**\n * Maximum number of records to load per page\n * @default 100\n */\n pageSize?: number;\n\n /**\n * Whether to show record count in header\n * @default true\n */\n showRecordCount?: boolean;\n\n /**\n * Placeholder text for the filter input\n * @default 'Filter records...'\n */\n filterPlaceholder?: string;\n\n /**\n * Debounce time for filter input in milliseconds\n * @default 250\n */\n filterDebounceMs?: number;\n\n /**\n * Custom grid column definitions (optional - auto-generated if not provided)\n */\n gridColumns?: GridColumnDef[];\n\n /**\n * Custom card template (optional - auto-generated if not provided)\n */\n cardTemplate?: CardTemplate;\n\n /**\n * Height of the component (CSS value)\n * @default '100%'\n */\n height?: string;\n\n /**\n * Whether to use server-side filtering via UserSearchString\n * When true, filter text is sent to the server for SQL-based filtering\n * When false, filtering is done client-side on loaded records\n * @default true\n */\n serverSideFiltering?: boolean;\n\n /**\n * Whether to use server-side sorting via OrderBy\n * When true, sort changes trigger a new server request\n * When false, sorting is done client-side by AG Grid\n * @default true\n */\n serverSideSorting?: boolean;\n\n /**\n * Whether to show pagination controls\n * @default true\n */\n showPagination?: boolean;\n\n /**\n * Default sort field when loading data\n */\n defaultSortField?: string;\n\n /**\n * Default sort direction when loading data\n * @default 'asc'\n */\n defaultSortDirection?: SortDirection;\n}\n\n/**\n * Timeline segment grouping options\n */\nexport type TimelineSegmentGrouping = 'none' | 'day' | 'week' | 'month' | 'quarter' | 'year';\n\n/**\n * Timeline orientation options\n */\nexport type TimelineOrientation = 'vertical' | 'horizontal';\n\n/**\n * Timeline-specific configuration state\n * Persisted in UserView.DisplayState JSON\n *\n * Extends the generated {@link MJUserViewEntity_ITimelineState} with Angular-specific fields.\n */\nexport type TimelineState = MJUserViewEntity_ITimelineState;\n\n/**\n * Card-specific configuration state\n * Persisted in UserView.DisplayState JSON\n *\n * Identical to the generated {@link MJUserViewEntity_IDisplayCardState}.\n */\nexport type CardState = MJUserViewEntity_IDisplayCardState;\n\n/**\n * Grid-specific configuration state\n * Persisted in UserView.DisplayState JSON\n * Note: Most grid state is already in GridState column, this is for additional settings\n *\n * Extends the generated {@link MJUserViewEntity_IGridDisplayState} with Angular-specific fields.\n */\nexport type GridDisplayState = MJUserViewEntity_IGridDisplayState;\n\n/**\n * View display state - persisted in UserView.DisplayState\n * Contains the default view mode and mode-specific configuration\n */\nexport interface ViewDisplayState {\n /** The default view mode to show when loading this view */\n defaultMode: EntityViewMode;\n /** Which view modes are enabled/visible for this view */\n enabledModes?: {\n grid?: boolean;\n cards?: boolean;\n timeline?: boolean;\n };\n /** Timeline-specific configuration */\n timeline?: TimelineState;\n /** Card-specific configuration */\n cards?: CardState;\n /** Grid-specific configuration */\n grid?: GridDisplayState;\n}\n\n/**\n * Default configuration values\n */\n/**\n * Summary of what a view configuration includes, for preview in Quick Save dialog\n */\nexport interface ViewConfigSummary {\n /** Number of visible columns */\n ColumnCount: number;\n /** Number of active filters */\n FilterCount: number;\n /** Number of sort levels */\n SortCount: number;\n /** Whether smart filter is active */\n SmartFilterActive: boolean;\n /** Smart filter prompt text (if active) */\n SmartFilterPrompt: string;\n /** Number of enabled aggregates */\n AggregateCount: number;\n}\n\n/**\n * Event emitted by QuickSaveDialog when user saves\n */\nexport interface QuickSaveEvent {\n /** View name */\n Name: string;\n /** View description (optional) */\n Description: string;\n /** Whether the view is shared */\n IsShared: boolean;\n /** Whether to save as new (true) or update existing (false) */\n SaveAsNew: boolean;\n}\n\n/**\n * Data emitted by QuickSaveDialog when user clicks \"Customize columns, filters & sorting...\"\n * Carries the partially-filled form data so the config panel can continue the new-view creation.\n */\nexport interface QuickSaveAdvancedEvent {\n /** View name entered so far */\n Name: string;\n /** View description entered so far */\n Description: string;\n /** Whether the view should be shared */\n IsShared: boolean;\n}\n\n/**\n * Result of a view save operation (for notification display)\n */\nexport interface ViewSaveResult {\n /** Whether the save succeeded */\n Success: boolean;\n /** Error message (on failure) */\n ErrorMessage?: string;\n /** Whether this was a new view or update */\n IsNew: boolean;\n}\n\nexport const DEFAULT_VIEWER_CONFIG: Required<EntityViewerConfig> = {\n chrome: 'workspace',\n showFilter: true,\n showViewModeToggle: true,\n selectionBehavior: 'emit-only',\n defaultViewMode: 'grid',\n enableMultiSelect: false,\n pageSize: 100,\n showRecordCount: true,\n filterPlaceholder: 'Filter records...',\n filterDebounceMs: 250,\n gridColumns: [],\n cardTemplate: {\n titleFields: [],\n subtitleField: null,\n descriptionField: null,\n displayFields: [],\n thumbnailFields: [],\n badgeField: null\n },\n height: '100%',\n serverSideFiltering: true,\n serverSideSorting: true,\n showPagination: true,\n defaultSortField: '',\n defaultSortDirection: 'asc'\n};\n\n/** Compact defaults applied when {@link EntityViewerConfig.chrome} is `embedded`. */\nexport const EMBEDDED_VIEWER_CHROME: Partial<EntityViewerConfig> = {\n showFilter: false,\n showViewModeToggle: false,\n showRecordCount: false,\n showPagination: false,\n pageSize: 8,\n};\n\n/**\n * Merge viewer config: workspace defaults, then embedded preset when requested, then explicit fields.\n * Explicit booleans always win over the chrome preset.\n */\nexport function ResolveViewerConfig(partial: Partial<EntityViewerConfig> = {}): Required<EntityViewerConfig> {\n const chrome = partial.chrome ?? 'workspace';\n const preset = chrome === 'embedded' ? EMBEDDED_VIEWER_CHROME : {};\n return {\n ...DEFAULT_VIEWER_CONFIG,\n ...preset,\n ...partial,\n chrome,\n };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"record.util.d.ts","sourceRoot":"","sources":["../../../src/lib/utils/record.util.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,EAAE,UAAU,GAAG,YAAY,CAMvG;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,EAAE,UAAU,GAAG,MAAM,CAE7F;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,wBAAwB,CAAC,CAAC,SAAS;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,EAChE,UAAU,EAAE,UAAU,EACtB,OAAO,EAAE,SAAS,CAAC,EAAE,GACtB,CAAC,EAAE,CAKL;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC7B,UAAU,EAAE,UAAU,EACtB,SAAS,CAAC,EAAE,aAAa,GAAG,IAAI,EAChC,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,GAC5C,MAAM,EAAE,CA2FV"}
1
+ {"version":3,"file":"record.util.d.ts","sourceRoot":"","sources":["../../../src/lib/utils/record.util.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,EAAE,UAAU,GAAG,YAAY,CAEvG;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,EAAE,UAAU,GAAG,MAAM,CAE7F;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,wBAAwB,CAAC,CAAC,SAAS;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,EAChE,UAAU,EAAE,UAAU,EACtB,OAAO,EAAE,SAAS,CAAC,EAAE,GACtB,CAAC,EAAE,CAKL;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC7B,UAAU,EAAE,UAAU,EACtB,SAAS,CAAC,EAAE,aAAa,GAAG,IAAI,EAChC,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,GAC5C,MAAM,EAAE,CA2FV"}
@@ -4,11 +4,7 @@ import { CompositeKey } from '@memberjunction/core';
4
4
  * Works with both plain objects (from ResultType: 'simple') and BaseEntity instances.
5
5
  */
6
6
  export function buildCompositeKey(record, entityInfo) {
7
- const kvps = entityInfo.PrimaryKeys.map(pk => ({
8
- FieldName: pk.Name,
9
- Value: record[pk.Name]
10
- }));
11
- return new CompositeKey(kvps);
7
+ return CompositeKey.FromEntityRecord(entityInfo, record);
12
8
  }
13
9
  /**
14
10
  * Build a PK concatenated string matching CompositeKey.ToConcatenatedString() format.
@@ -1 +1 @@
1
- {"version":3,"file":"record.util.js","sourceRoot":"","sources":["../../../src/lib/utils/record.util.ts"],"names":[],"mappings":"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","sourcesContent":["import { EntityInfo, CompositeKey } from '@memberjunction/core';\nimport { ViewGridState } from '../types';\n\n/**\n * Build a CompositeKey from a plain record using EntityInfo PK fields.\n * Works with both plain objects (from ResultType: 'simple') and BaseEntity instances.\n */\nexport function buildCompositeKey(record: Record<string, unknown>, entityInfo: EntityInfo): CompositeKey {\n const kvps = entityInfo.PrimaryKeys.map(pk => ({\n FieldName: pk.Name,\n Value: record[pk.Name]\n }));\n return new CompositeKey(kvps);\n}\n\n/**\n * Build a PK concatenated string matching CompositeKey.ToConcatenatedString() format.\n * Used as a stable row identifier for selection, tracking, and map keys.\n */\nexport function buildPkString(record: Record<string, unknown>, entityInfo: EntityInfo): string {\n return buildCompositeKey(record, entityInfo).ToConcatenatedString();\n}\n\n/**\n * Rewrite host-supplied column field names to the entity's OWN spelling.\n *\n * A page may reasonably write `field: 'name'` where the entity says `Name`, and the grid resolves\n * host names case-insensitively when it looks up metadata — so such a column *renders*. But the\n * spelling does not stay cosmetic: it becomes the AG Grid colDef's `field`, and therefore its\n * `colId`, and therefore the key in captured grid state and sort settings. Meanwhile the row objects\n * the grid builds are keyed by the ENTITY's spelling. Anything that addresses data or matches state\n * by that name then misses: cells resolve to undefined, the captured state drops the column, a saved\n * sort is discarded.\n *\n * Normalising once, here, is what keeps those two vocabularies from diverging — the alternative is a\n * case-insensitive comparison at every one of those sites, which is the state that produced the bug.\n *\n * Unchanged columns are returned by REFERENCE (so callers can detect a no-op cheaply), and changed\n * ones as copies — the host's own array and objects are never mutated. A name matching no field is\n * left alone for the existing validation to reject.\n */\nexport function canonicalizeColumnFields<T extends { field: string }>(\n entityInfo: EntityInfo,\n columns: readonly T[],\n): T[] {\n return columns.map(col => {\n const field = entityInfo.Fields.find(f => f.Name.toLowerCase() === col.field.toLowerCase());\n return field && field.Name !== col.field ? { ...col, field: field.Name } : col;\n });\n}\n\n/**\n * Compute the Fields array to request from RunView based on EntityInfo and optional grid state.\n * Includes: PK fields, NameField, visible display fields, and timestamp fields.\n */\nexport function computeFieldsList(\n entityInfo: EntityInfo,\n gridState?: ViewGridState | null,\n hostColumnFields?: readonly string[] | null,\n): string[] {\n const fields = new Set<string>();\n\n // Fields the HOST explicitly asked to display. Without these, a page that supplies `[Columns]`\n // renders columns whose data was never fetched — every cell shows as empty, which reads as a\n // data bug rather than a missing SELECT. They are added unconditionally (not as an alternative\n // to the branches below) because a host column list says what to SHOW, while DefaultInView and\n // the grid state say what to fetch by default; the host's columns must be a superset, not a\n // replacement. Names are validated against the entity so a stale column can't break the query.\n if (hostColumnFields?.length) {\n for (const name of hostColumnFields) {\n const field = entityInfo.Fields.find(f => f.Name.toLowerCase() === name.toLowerCase());\n // Add the ENTITY's spelling, not the host's. A page may reasonably write `field: 'name'`\n // where the entity says `Name`, and metadata lookups here are case-insensitive so the\n // column is accepted — but this list goes on the wire. On the GraphQL transport it is\n // interpolated straight into the selection set, and GraphQL field names are case\n // SENSITIVE, so the host's spelling produces `Cannot query field \"name\"` and the whole\n // view fails: an error and zero rows, not one odd column. (This Set is case-sensitive\n // too, so the two spellings would not even collapse into one entry.)\n //\n // Callers that hand this function raw host names should also run them through\n // `canonicalizeColumnFields` for the column definitions, or the rendered column will\n // address a key the fetched rows do not have.\n if (field) {\n fields.add(field.Name);\n }\n }\n }\n\n // Always include PK fields\n for (const pk of entityInfo.PrimaryKeys) {\n fields.add(pk.Name);\n }\n\n // Include NameField for display name resolution\n if (entityInfo.NameField) {\n fields.add(entityInfo.NameField.Name);\n }\n\n // Include visible fields from gridState or DefaultInView\n if (gridState?.columnSettings?.length) {\n for (const col of gridState.columnSettings) {\n if (!col.hidden) {\n fields.add(col.Name);\n }\n }\n } else {\n // First try to use DefaultInView fields\n const defaultInViewFields = entityInfo.Fields.filter(f => f.DefaultInView);\n\n if (defaultInViewFields.length > 0) {\n for (const f of defaultInViewFields) {\n fields.add(f.Name);\n }\n } else {\n // Fallback: when no DefaultInView fields are defined, include the first 10\n // non-system fields. This matches the fallback in generateAgColumnDefs()\n // which uses getDefaultFieldsFallback() to show columns even when\n // DefaultInView isn't configured.\n const fallbackFields = entityInfo.Fields\n .filter(f =>\n !f.Name.startsWith('__mj_') &&\n !(f.IsPrimaryKey && f.SQLFullType?.toLowerCase() === 'uniqueidentifier') &&\n (f.Length <= 500 || f.Length < 0) // Exclude very long text unless nvarchar(max)\n )\n .slice(0, 10);\n\n for (const f of fallbackFields) {\n fields.add(f.Name);\n }\n }\n }\n\n // Include __mj timestamp fields (commonly used for sort and display)\n fields.add('__mj_CreatedAt');\n fields.add('__mj_UpdatedAt');\n\n // Lat/lng only — never address-tagged fields. The map uses pre-geocoded\n // coords, never text-based location guessing.\n for (const f of entityInfo.Fields) {\n if (f.ExtendedType === 'GeoLatitude' || f.ExtendedType === 'GeoLongitude') {\n fields.add(f.Name);\n }\n }\n // Gated on SupportsGeoCoding — BoundaryGeoJSON is nvarchar(max), wasteful\n // to pull for grid views that won't render a map.\n if (entityInfo.SupportsGeoCoding && entityInfo.Fields.some(f => f.Name === 'BoundaryGeoJSON')) {\n fields.add('BoundaryGeoJSON');\n }\n\n return Array.from(fields);\n}\n"]}
1
+ {"version":3,"file":"record.util.js","sourceRoot":"","sources":["../../../src/lib/utils/record.util.ts"],"names":[],"mappings":"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","sourcesContent":["import { EntityInfo, CompositeKey } from '@memberjunction/core';\nimport { ViewGridState } from '../types';\n\n/**\n * Build a CompositeKey from a plain record using EntityInfo PK fields.\n * Works with both plain objects (from ResultType: 'simple') and BaseEntity instances.\n */\nexport function buildCompositeKey(record: Record<string, unknown>, entityInfo: EntityInfo): CompositeKey {\n return CompositeKey.FromEntityRecord(entityInfo, record);\n}\n\n/**\n * Build a PK concatenated string matching CompositeKey.ToConcatenatedString() format.\n * Used as a stable row identifier for selection, tracking, and map keys.\n */\nexport function buildPkString(record: Record<string, unknown>, entityInfo: EntityInfo): string {\n return buildCompositeKey(record, entityInfo).ToConcatenatedString();\n}\n\n/**\n * Rewrite host-supplied column field names to the entity's OWN spelling.\n *\n * A page may reasonably write `field: 'name'` where the entity says `Name`, and the grid resolves\n * host names case-insensitively when it looks up metadata — so such a column *renders*. But the\n * spelling does not stay cosmetic: it becomes the AG Grid colDef's `field`, and therefore its\n * `colId`, and therefore the key in captured grid state and sort settings. Meanwhile the row objects\n * the grid builds are keyed by the ENTITY's spelling. Anything that addresses data or matches state\n * by that name then misses: cells resolve to undefined, the captured state drops the column, a saved\n * sort is discarded.\n *\n * Normalising once, here, is what keeps those two vocabularies from diverging — the alternative is a\n * case-insensitive comparison at every one of those sites, which is the state that produced the bug.\n *\n * Unchanged columns are returned by REFERENCE (so callers can detect a no-op cheaply), and changed\n * ones as copies — the host's own array and objects are never mutated. A name matching no field is\n * left alone for the existing validation to reject.\n */\nexport function canonicalizeColumnFields<T extends { field: string }>(\n entityInfo: EntityInfo,\n columns: readonly T[],\n): T[] {\n return columns.map(col => {\n const field = entityInfo.Fields.find(f => f.Name.toLowerCase() === col.field.toLowerCase());\n return field && field.Name !== col.field ? { ...col, field: field.Name } : col;\n });\n}\n\n/**\n * Compute the Fields array to request from RunView based on EntityInfo and optional grid state.\n * Includes: PK fields, NameField, visible display fields, and timestamp fields.\n */\nexport function computeFieldsList(\n entityInfo: EntityInfo,\n gridState?: ViewGridState | null,\n hostColumnFields?: readonly string[] | null,\n): string[] {\n const fields = new Set<string>();\n\n // Fields the HOST explicitly asked to display. Without these, a page that supplies `[Columns]`\n // renders columns whose data was never fetched — every cell shows as empty, which reads as a\n // data bug rather than a missing SELECT. They are added unconditionally (not as an alternative\n // to the branches below) because a host column list says what to SHOW, while DefaultInView and\n // the grid state say what to fetch by default; the host's columns must be a superset, not a\n // replacement. Names are validated against the entity so a stale column can't break the query.\n if (hostColumnFields?.length) {\n for (const name of hostColumnFields) {\n const field = entityInfo.Fields.find(f => f.Name.toLowerCase() === name.toLowerCase());\n // Add the ENTITY's spelling, not the host's. A page may reasonably write `field: 'name'`\n // where the entity says `Name`, and metadata lookups here are case-insensitive so the\n // column is accepted — but this list goes on the wire. On the GraphQL transport it is\n // interpolated straight into the selection set, and GraphQL field names are case\n // SENSITIVE, so the host's spelling produces `Cannot query field \"name\"` and the whole\n // view fails: an error and zero rows, not one odd column. (This Set is case-sensitive\n // too, so the two spellings would not even collapse into one entry.)\n //\n // Callers that hand this function raw host names should also run them through\n // `canonicalizeColumnFields` for the column definitions, or the rendered column will\n // address a key the fetched rows do not have.\n if (field) {\n fields.add(field.Name);\n }\n }\n }\n\n // Always include PK fields\n for (const pk of entityInfo.PrimaryKeys) {\n fields.add(pk.Name);\n }\n\n // Include NameField for display name resolution\n if (entityInfo.NameField) {\n fields.add(entityInfo.NameField.Name);\n }\n\n // Include visible fields from gridState or DefaultInView\n if (gridState?.columnSettings?.length) {\n for (const col of gridState.columnSettings) {\n if (!col.hidden) {\n fields.add(col.Name);\n }\n }\n } else {\n // First try to use DefaultInView fields\n const defaultInViewFields = entityInfo.Fields.filter(f => f.DefaultInView);\n\n if (defaultInViewFields.length > 0) {\n for (const f of defaultInViewFields) {\n fields.add(f.Name);\n }\n } else {\n // Fallback: when no DefaultInView fields are defined, include the first 10\n // non-system fields. This matches the fallback in generateAgColumnDefs()\n // which uses getDefaultFieldsFallback() to show columns even when\n // DefaultInView isn't configured.\n const fallbackFields = entityInfo.Fields\n .filter(f =>\n !f.Name.startsWith('__mj_') &&\n !(f.IsPrimaryKey && f.SQLFullType?.toLowerCase() === 'uniqueidentifier') &&\n (f.Length <= 500 || f.Length < 0) // Exclude very long text unless nvarchar(max)\n )\n .slice(0, 10);\n\n for (const f of fallbackFields) {\n fields.add(f.Name);\n }\n }\n }\n\n // Include __mj timestamp fields (commonly used for sort and display)\n fields.add('__mj_CreatedAt');\n fields.add('__mj_UpdatedAt');\n\n // Lat/lng only — never address-tagged fields. The map uses pre-geocoded\n // coords, never text-based location guessing.\n for (const f of entityInfo.Fields) {\n if (f.ExtendedType === 'GeoLatitude' || f.ExtendedType === 'GeoLongitude') {\n fields.add(f.Name);\n }\n }\n // Gated on SupportsGeoCoding — BoundaryGeoJSON is nvarchar(max), wasteful\n // to pull for grid views that won't render a map.\n if (entityInfo.SupportsGeoCoding && entityInfo.Fields.some(f => f.Name === 'BoundaryGeoJSON')) {\n fields.add('BoundaryGeoJSON');\n }\n\n return Array.from(fields);\n}\n"]}