@memberjunction/ng-list-management 5.34.1 → 5.36.0

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 (63) hide show
  1. package/README.md +179 -85
  2. package/dist/lib/components/audience-source-picker/audience-source-picker.component.d.ts +74 -0
  3. package/dist/lib/components/audience-source-picker/audience-source-picker.component.d.ts.map +1 -0
  4. package/dist/lib/components/audience-source-picker/audience-source-picker.component.js +396 -0
  5. package/dist/lib/components/audience-source-picker/audience-source-picker.component.js.map +1 -0
  6. package/dist/lib/components/audience-source-summary/audience-source-summary.component.d.ts +34 -0
  7. package/dist/lib/components/audience-source-summary/audience-source-summary.component.d.ts.map +1 -0
  8. package/dist/lib/components/audience-source-summary/audience-source-summary.component.js +136 -0
  9. package/dist/lib/components/audience-source-summary/audience-source-summary.component.js.map +1 -0
  10. package/dist/lib/components/list-audit-log/list-audit-log.component.d.ts +55 -0
  11. package/dist/lib/components/list-audit-log/list-audit-log.component.d.ts.map +1 -0
  12. package/dist/lib/components/list-audit-log/list-audit-log.component.js +280 -0
  13. package/dist/lib/components/list-audit-log/list-audit-log.component.js.map +1 -0
  14. package/dist/lib/components/list-delta-confirm/list-delta-confirm.component.d.ts +64 -0
  15. package/dist/lib/components/list-delta-confirm/list-delta-confirm.component.d.ts.map +1 -0
  16. package/dist/lib/components/list-delta-confirm/list-delta-confirm.component.js +323 -0
  17. package/dist/lib/components/list-delta-confirm/list-delta-confirm.component.js.map +1 -0
  18. package/dist/lib/components/list-invitations/list-invitations.component.d.ts +60 -0
  19. package/dist/lib/components/list-invitations/list-invitations.component.d.ts.map +1 -0
  20. package/dist/lib/components/list-invitations/list-invitations.component.js +367 -0
  21. package/dist/lib/components/list-invitations/list-invitations.component.js.map +1 -0
  22. package/dist/lib/components/list-management-dialog/list-management-dialog.component.d.ts.map +1 -1
  23. package/dist/lib/components/list-management-dialog/list-management-dialog.component.js +0 -4
  24. package/dist/lib/components/list-management-dialog/list-management-dialog.component.js.map +1 -1
  25. package/dist/lib/components/list-share-dialog/list-share-dialog.component.d.ts +18 -1
  26. package/dist/lib/components/list-share-dialog/list-share-dialog.component.d.ts.map +1 -1
  27. package/dist/lib/components/list-share-dialog/list-share-dialog.component.js +115 -66
  28. package/dist/lib/components/list-share-dialog/list-share-dialog.component.js.map +1 -1
  29. package/dist/lib/components/list-stats/list-stats.component.d.ts +52 -0
  30. package/dist/lib/components/list-stats/list-stats.component.d.ts.map +1 -0
  31. package/dist/lib/components/list-stats/list-stats.component.js +283 -0
  32. package/dist/lib/components/list-stats/list-stats.component.js.map +1 -0
  33. package/dist/lib/components/save-view-as-list-dialog/save-view-as-list-dialog.component.d.ts +68 -0
  34. package/dist/lib/components/save-view-as-list-dialog/save-view-as-list-dialog.component.d.ts.map +1 -0
  35. package/dist/lib/components/save-view-as-list-dialog/save-view-as-list-dialog.component.js +316 -0
  36. package/dist/lib/components/save-view-as-list-dialog/save-view-as-list-dialog.component.js.map +1 -0
  37. package/dist/lib/components/shared-with-me/shared-with-me.component.d.ts +47 -0
  38. package/dist/lib/components/shared-with-me/shared-with-me.component.d.ts.map +1 -0
  39. package/dist/lib/components/shared-with-me/shared-with-me.component.js +231 -0
  40. package/dist/lib/components/shared-with-me/shared-with-me.component.js.map +1 -0
  41. package/dist/lib/components/tag-chips/tag-chips.component.d.ts +85 -0
  42. package/dist/lib/components/tag-chips/tag-chips.component.d.ts.map +1 -0
  43. package/dist/lib/components/tag-chips/tag-chips.component.js +392 -0
  44. package/dist/lib/components/tag-chips/tag-chips.component.js.map +1 -0
  45. package/dist/lib/models/list-sharing.models.d.ts +5 -0
  46. package/dist/lib/models/list-sharing.models.d.ts.map +1 -1
  47. package/dist/lib/models/list-sharing.models.js.map +1 -1
  48. package/dist/lib/module.d.ts +17 -8
  49. package/dist/lib/module.d.ts.map +1 -1
  50. package/dist/lib/module.js +51 -6
  51. package/dist/lib/module.js.map +1 -1
  52. package/dist/lib/services/list-management.service.d.ts.map +1 -1
  53. package/dist/lib/services/list-management.service.js +0 -9
  54. package/dist/lib/services/list-management.service.js.map +1 -1
  55. package/dist/lib/services/list-sharing.service.d.ts +8 -3
  56. package/dist/lib/services/list-sharing.service.d.ts.map +1 -1
  57. package/dist/lib/services/list-sharing.service.js +33 -15
  58. package/dist/lib/services/list-sharing.service.js.map +1 -1
  59. package/dist/public-api.d.ts +9 -0
  60. package/dist/public-api.d.ts.map +1 -1
  61. package/dist/public-api.js +9 -0
  62. package/dist/public-api.js.map +1 -1
  63. package/package.json +13 -11
@@ -0,0 +1,136 @@
1
+ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, inject } from '@angular/core';
2
+ import { BaseAngularComponent } from '@memberjunction/ng-base-types';
3
+ import { Metadata } from '@memberjunction/core';
4
+ import * as i0 from "@angular/core";
5
+ function AudienceSourceSummaryComponent_Conditional_0_Conditional_10_Template(rf, ctx) { if (rf & 1) {
6
+ i0.ɵɵtext(0, " \u00B7 resolves to ");
7
+ i0.ɵɵelementStart(1, "strong");
8
+ i0.ɵɵtext(2);
9
+ i0.ɵɵelementEnd();
10
+ } if (rf & 2) {
11
+ const ctx_r0 = i0.ɵɵnextContext(2);
12
+ i0.ɵɵadvance(2);
13
+ i0.ɵɵtextInterpolate1("", ctx_r0.RecordCount, " record(s)");
14
+ } }
15
+ function AudienceSourceSummaryComponent_Conditional_0_Conditional_11_Template(rf, ctx) { if (rf & 1) {
16
+ i0.ɵɵtext(0, " \u00B7 resolves at execution time ");
17
+ } }
18
+ function AudienceSourceSummaryComponent_Conditional_0_Template(rf, ctx) { if (rf & 1) {
19
+ i0.ɵɵelementStart(0, "div", 0);
20
+ i0.ɵɵelement(1, "i");
21
+ i0.ɵɵelementStart(2, "div", 2)(3, "div", 3);
22
+ i0.ɵɵtext(4, " Audience: ");
23
+ i0.ɵɵelementStart(5, "strong");
24
+ i0.ɵɵtext(6);
25
+ i0.ɵɵelementEnd()();
26
+ i0.ɵɵelementStart(7, "div", 4)(8, "span", 5);
27
+ i0.ɵɵtext(9);
28
+ i0.ɵɵelementEnd();
29
+ i0.ɵɵconditionalCreate(10, AudienceSourceSummaryComponent_Conditional_0_Conditional_10_Template, 3, 1)(11, AudienceSourceSummaryComponent_Conditional_0_Conditional_11_Template, 1, 0);
30
+ i0.ɵɵelementEnd()()();
31
+ } if (rf & 2) {
32
+ const ctx_r0 = i0.ɵɵnextContext();
33
+ i0.ɵɵadvance();
34
+ i0.ɵɵclassMap(ctx_r0.icon);
35
+ i0.ɵɵadvance(5);
36
+ i0.ɵɵtextInterpolate(ctx_r0.label);
37
+ i0.ɵɵadvance(3);
38
+ i0.ɵɵtextInterpolate(ctx_r0.kindLabel);
39
+ i0.ɵɵadvance();
40
+ i0.ɵɵconditional(ctx_r0.RecordCount != null ? 10 : 11);
41
+ } }
42
+ function AudienceSourceSummaryComponent_Conditional_1_Template(rf, ctx) { if (rf & 1) {
43
+ i0.ɵɵelementStart(0, "div", 1);
44
+ i0.ɵɵelement(1, "i", 6);
45
+ i0.ɵɵelementStart(2, "span");
46
+ i0.ɵɵtext(3, "No audience selected.");
47
+ i0.ɵɵelementEnd()();
48
+ } }
49
+ /**
50
+ * Compact, read-only summary of an `AudienceSource`. Resolves names
51
+ * (list / view → display name) so the user sees the audience as
52
+ * "VIP Donors Q4 2026 (List, 348 records)" rather than a raw UUID.
53
+ *
54
+ * Pairs naturally with `AudienceSourcePickerComponent`: bind the same
55
+ * `Source` to both, and the summary reflects the picker's current
56
+ * selection live.
57
+ */
58
+ export class AudienceSourceSummaryComponent extends BaseAngularComponent {
59
+ cdr = inject(ChangeDetectorRef);
60
+ set Source(value) {
61
+ this._source = value;
62
+ if (value)
63
+ void this.resolveLabel(value);
64
+ else
65
+ this.label = null;
66
+ }
67
+ get Source() {
68
+ return this._source;
69
+ }
70
+ _source = null;
71
+ /** Optional pre-computed count to surface (e.g., from a prior resolve). */
72
+ RecordCount = null;
73
+ label = null;
74
+ icon = 'fa-solid fa-bullseye';
75
+ get kindLabel() {
76
+ if (!this._source)
77
+ return '';
78
+ if (this._source.kind === 'list')
79
+ return 'List';
80
+ if (this._source.kind === 'view')
81
+ return 'View';
82
+ return 'Ad-hoc Filter';
83
+ }
84
+ /**
85
+ * Resolve a human-readable label for the source. Names are looked up
86
+ * one-at-a-time; cheap because each picker change fires at most once.
87
+ * Falls back to the underlying ID if the lookup fails — the summary
88
+ * is informational, never load-bearing.
89
+ */
90
+ async resolveLabel(source) {
91
+ try {
92
+ const md = this.ProviderToUse ?? new Metadata();
93
+ if (source.kind === 'list') {
94
+ const list = await md.GetEntityObject('MJ: Lists', this.ProviderToUse.CurrentUser ?? undefined);
95
+ const loaded = await list.Load(source.listId);
96
+ this.label = loaded ? list.Name : source.listId;
97
+ this.icon = 'fa-solid fa-list';
98
+ }
99
+ else if (source.kind === 'view') {
100
+ const view = await md.GetEntityObject('MJ: User Views', this.ProviderToUse.CurrentUser ?? undefined);
101
+ const loaded = await view.Load(source.viewId);
102
+ this.label = loaded ? view.Name : source.viewId;
103
+ this.icon = 'fa-solid fa-eye';
104
+ }
105
+ else {
106
+ this.label = `${source.entityName} — ${this.truncate(source.extraFilter, 80)}`;
107
+ this.icon = 'fa-solid fa-filter';
108
+ }
109
+ }
110
+ catch {
111
+ this.label = '(unable to resolve)';
112
+ }
113
+ finally {
114
+ this.cdr.markForCheck();
115
+ }
116
+ }
117
+ truncate(s, n) {
118
+ return s.length <= n ? s : `${s.slice(0, n - 1)}…`;
119
+ }
120
+ static ɵfac = /*@__PURE__*/ (() => { let ɵAudienceSourceSummaryComponent_BaseFactory; return function AudienceSourceSummaryComponent_Factory(__ngFactoryType__) { return (ɵAudienceSourceSummaryComponent_BaseFactory || (ɵAudienceSourceSummaryComponent_BaseFactory = i0.ɵɵgetInheritedFactory(AudienceSourceSummaryComponent)))(__ngFactoryType__ || AudienceSourceSummaryComponent); }; })();
121
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: AudienceSourceSummaryComponent, selectors: [["mj-audience-source-summary"]], inputs: { Source: "Source", RecordCount: "RecordCount" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 2, vars: 1, consts: [[1, "audience-summary"], [1, "audience-summary", "audience-summary--empty"], [1, "audience-summary__body"], [1, "audience-summary__name"], [1, "audience-summary__detail"], [1, "audience-summary__kind"], [1, "fa-solid", "fa-bullseye"]], template: function AudienceSourceSummaryComponent_Template(rf, ctx) { if (rf & 1) {
122
+ i0.ɵɵconditionalCreate(0, AudienceSourceSummaryComponent_Conditional_0_Template, 12, 5, "div", 0)(1, AudienceSourceSummaryComponent_Conditional_1_Template, 4, 0, "div", 1);
123
+ } if (rf & 2) {
124
+ i0.ɵɵconditional(ctx.Source && ctx.label ? 0 : 1);
125
+ } }, styles: [".audience-summary[_ngcontent-%COMP%] {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n padding: 10px 12px;\n background: var(--mj-bg-surface-sunken);\n border: 1px solid var(--mj-border-subtle);\n border-radius: 8px;\n font-size: 13px;\n}\n\n.audience-summary--empty[_ngcontent-%COMP%] {\n color: var(--mj-text-muted);\n font-style: italic;\n align-items: center;\n}\n\n.audience-summary[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n margin-top: 2px;\n}\n\n.audience-summary__body[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 0;\n}\n\n.audience-summary__name[_ngcontent-%COMP%] {\n font-weight: 500;\n}\n\n.audience-summary__detail[_ngcontent-%COMP%] {\n font-size: 11.5px;\n color: var(--mj-text-muted);\n margin-top: 2px;\n}\n\n.audience-summary__kind[_ngcontent-%COMP%] {\n display: inline-block;\n padding: 1px 6px;\n border-radius: 999px;\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-size: 10.5px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n margin-right: 4px;\n}"], changeDetection: 0 });
126
+ }
127
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(AudienceSourceSummaryComponent, [{
128
+ type: Component,
129
+ args: [{ standalone: false, selector: 'mj-audience-source-summary', changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (Source && label) {\n <div class=\"audience-summary\">\n <i [class]=\"icon\"></i>\n <div class=\"audience-summary__body\">\n <div class=\"audience-summary__name\">\n Audience: <strong>{{ label }}</strong>\n </div>\n <div class=\"audience-summary__detail\">\n <span class=\"audience-summary__kind\">{{ kindLabel }}</span>\n @if (RecordCount != null) {\n \u00B7 resolves to <strong>{{ RecordCount }} record(s)</strong>\n } @else {\n \u00B7 resolves at execution time\n }\n </div>\n </div>\n </div>\n} @else {\n <div class=\"audience-summary audience-summary--empty\">\n <i class=\"fa-solid fa-bullseye\"></i>\n <span>No audience selected.</span>\n </div>\n}\n", styles: [".audience-summary {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n padding: 10px 12px;\n background: var(--mj-bg-surface-sunken);\n border: 1px solid var(--mj-border-subtle);\n border-radius: 8px;\n font-size: 13px;\n}\n\n.audience-summary--empty {\n color: var(--mj-text-muted);\n font-style: italic;\n align-items: center;\n}\n\n.audience-summary i {\n color: var(--mj-brand-primary);\n margin-top: 2px;\n}\n\n.audience-summary__body {\n flex: 1;\n min-width: 0;\n}\n\n.audience-summary__name {\n font-weight: 500;\n}\n\n.audience-summary__detail {\n font-size: 11.5px;\n color: var(--mj-text-muted);\n margin-top: 2px;\n}\n\n.audience-summary__kind {\n display: inline-block;\n padding: 1px 6px;\n border-radius: 999px;\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-size: 10.5px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n margin-right: 4px;\n}\n"] }]
130
+ }], null, { Source: [{
131
+ type: Input
132
+ }], RecordCount: [{
133
+ type: Input
134
+ }] }); })();
135
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(AudienceSourceSummaryComponent, { className: "AudienceSourceSummaryComponent", filePath: "src/lib/components/audience-source-summary/audience-source-summary.component.ts", lineNumber: 24 }); })();
136
+ //# sourceMappingURL=audience-source-summary.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audience-source-summary.component.js","sourceRoot":"","sources":["../../../../src/lib/components/audience-source-summary/audience-source-summary.component.ts","../../../../src/lib/components/audience-source-summary/audience-source-summary.component.html"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAErG,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;;;ICOtC,oCAAc;IAAA,8BAAQ;IAAA,YAA2B;IAAA,iBAAS;;;IAApC,eAA2B;IAA3B,2DAA2B;;;IAEjD,mDACF;;;IAZN,8BAA8B;IAC5B,oBAAsB;IAEpB,AADF,8BAAoC,aACE;IAClC,2BAAU;IAAA,8BAAQ;IAAA,YAAW;IAC/B,AAD+B,iBAAS,EAClC;IAEJ,AADF,8BAAsC,cACC;IAAA,YAAe;IAAA,iBAAO;IAGzD,AAFF,sGAA2B,gFAElB;IAKf,AADE,AADE,iBAAM,EACF,EACF;;;IAdD,cAAc;IAAd,0BAAc;IAGK,eAAW;IAAX,kCAAW;IAGQ,eAAe;IAAf,sCAAe;IACpD,cAIC;IAJD,sDAIC;;;IAKP,8BAAsD;IACpD,uBAAoC;IACpC,4BAAM;IAAA,qCAAqB;IAC7B,AAD6B,iBAAO,EAC9B;;ADdR;;;;;;;;GAQG;AAQH,MAAM,OAAO,8BAA+B,SAAQ,oBAAoB;IACrD,GAAG,GAAG,MAAM,CAAC,iBAAiB,CAAC,CAAC;IAEjD,IACI,MAAM,CAAC,KAA4B;QACrC,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;QACrB,IAAI,KAAK;YAAE,KAAK,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;;YACpC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;IACzB,CAAC;IACD,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IACO,OAAO,GAA0B,IAAI,CAAC;IAE9C,2EAA2E;IAClE,WAAW,GAAkB,IAAI,CAAC;IAEpC,KAAK,GAAkB,IAAI,CAAC;IAC5B,IAAI,GAAG,sBAAsB,CAAC;IAErC,IAAW,SAAS;QAClB,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO,EAAE,CAAC;QAC7B,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,MAAM;YAAE,OAAO,MAAM,CAAC;QAChD,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,MAAM;YAAE,OAAO,MAAM,CAAC;QAChD,OAAO,eAAe,CAAC;IACzB,CAAC;IAED;;;;;OAKG;IACK,KAAK,CAAC,YAAY,CAAC,MAAsB;QAC/C,IAAI,CAAC;YACH,MAAM,EAAE,GAAI,IAAI,CAAC,aAAqC,IAAI,IAAI,QAAQ,EAAE,CAAC;YACzE,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;gBAC3B,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,eAAe,CAAe,WAAW,EAAE,IAAI,CAAC,aAAa,CAAC,WAAW,IAAI,SAAS,CAAC,CAAC;gBAC9G,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;gBAC9C,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC;gBAChD,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;YACjC,CAAC;iBAAM,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;gBAClC,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,eAAe,CAAmB,gBAAgB,EAAE,IAAI,CAAC,aAAa,CAAC,WAAW,IAAI,SAAS,CAAC,CAAC;gBACvH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;gBAC9C,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC;gBAChD,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;YAChC,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,KAAK,GAAG,GAAG,MAAM,CAAC,UAAU,MAAM,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,WAAW,EAAE,EAAE,CAAC,EAAE,CAAC;gBAC/E,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;YACnC,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,CAAC,KAAK,GAAG,qBAAqB,CAAC;QACrC,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;QAC1B,CAAC;IACH,CAAC;IAEO,QAAQ,CAAC,CAAS,EAAE,CAAS;QACnC,OAAO,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC;IACrD,CAAC;qSA3DU,8BAA8B,yBAA9B,8BAA8B;6DAA9B,8BAA8B;YCNzC,AAjBF,iGAAuB,0EAiBd;;YAjBT,iDAsBC;;;iFDCY,8BAA8B;cAP1C,SAAS;6BACI,KAAK,YACP,4BAA4B,mBAGrB,uBAAuB,CAAC,MAAM;;kBAK9C,KAAK;;kBAYL,KAAK;;kFAfK,8BAA8B","sourcesContent":["import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, inject } from '@angular/core';\n\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\nimport { Metadata } from '@memberjunction/core';\nimport type { MJListEntity, MJUserViewEntity } from '@memberjunction/core-entities';\nimport type { AudienceSource } from '@memberjunction/lists-base';\n\n/**\n * Compact, read-only summary of an `AudienceSource`. Resolves names\n * (list / view → display name) so the user sees the audience as\n * \"VIP Donors Q4 2026 (List, 348 records)\" rather than a raw UUID.\n *\n * Pairs naturally with `AudienceSourcePickerComponent`: bind the same\n * `Source` to both, and the summary reflects the picker's current\n * selection live.\n */\n@Component({\n standalone: false,\n selector: 'mj-audience-source-summary',\n templateUrl: './audience-source-summary.component.html',\n styleUrls: ['./audience-source-summary.component.css'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class AudienceSourceSummaryComponent extends BaseAngularComponent {\n private readonly cdr = inject(ChangeDetectorRef);\n\n @Input()\n set Source(value: AudienceSource | null) {\n this._source = value;\n if (value) void this.resolveLabel(value);\n else this.label = null;\n }\n get Source(): AudienceSource | null {\n return this._source;\n }\n private _source: AudienceSource | null = null;\n\n /** Optional pre-computed count to surface (e.g., from a prior resolve). */\n @Input() RecordCount: number | null = null;\n\n public label: string | null = null;\n public icon = 'fa-solid fa-bullseye';\n\n public get kindLabel(): string {\n if (!this._source) return '';\n if (this._source.kind === 'list') return 'List';\n if (this._source.kind === 'view') return 'View';\n return 'Ad-hoc Filter';\n }\n\n /**\n * Resolve a human-readable label for the source. Names are looked up\n * one-at-a-time; cheap because each picker change fires at most once.\n * Falls back to the underlying ID if the lookup fails — the summary\n * is informational, never load-bearing.\n */\n private async resolveLabel(source: AudienceSource): Promise<void> {\n try {\n const md = (this.ProviderToUse as unknown as Metadata) ?? new Metadata();\n if (source.kind === 'list') {\n const list = await md.GetEntityObject<MJListEntity>('MJ: Lists', this.ProviderToUse.CurrentUser ?? undefined);\n const loaded = await list.Load(source.listId);\n this.label = loaded ? list.Name : source.listId;\n this.icon = 'fa-solid fa-list';\n } else if (source.kind === 'view') {\n const view = await md.GetEntityObject<MJUserViewEntity>('MJ: User Views', this.ProviderToUse.CurrentUser ?? undefined);\n const loaded = await view.Load(source.viewId);\n this.label = loaded ? view.Name : source.viewId;\n this.icon = 'fa-solid fa-eye';\n } else {\n this.label = `${source.entityName} — ${this.truncate(source.extraFilter, 80)}`;\n this.icon = 'fa-solid fa-filter';\n }\n } catch {\n this.label = '(unable to resolve)';\n } finally {\n this.cdr.markForCheck();\n }\n }\n\n private truncate(s: string, n: number): string {\n return s.length <= n ? s : `${s.slice(0, n - 1)}…`;\n }\n}\n","@if (Source && label) {\n <div class=\"audience-summary\">\n <i [class]=\"icon\"></i>\n <div class=\"audience-summary__body\">\n <div class=\"audience-summary__name\">\n Audience: <strong>{{ label }}</strong>\n </div>\n <div class=\"audience-summary__detail\">\n <span class=\"audience-summary__kind\">{{ kindLabel }}</span>\n @if (RecordCount != null) {\n · resolves to <strong>{{ RecordCount }} record(s)</strong>\n } @else {\n · resolves at execution time\n }\n </div>\n </div>\n </div>\n} @else {\n <div class=\"audience-summary audience-summary--empty\">\n <i class=\"fa-solid fa-bullseye\"></i>\n <span>No audience selected.</span>\n </div>\n}\n"]}
@@ -0,0 +1,55 @@
1
+ import { OnInit } from '@angular/core';
2
+ import { BaseAngularComponent } from '@memberjunction/ng-base-types';
3
+ import * as i0 from "@angular/core";
4
+ /**
5
+ * Audit log view for a single List (mockup 18). Reads from the generic
6
+ * `MJ: Audit Logs` entity, scoped to this list via `EntityID` (the MJ:
7
+ * Lists entity row) + `RecordID` = the list ID. The event-type dropdown
8
+ * is populated lazily from the audit-log-type rows actually present in
9
+ * the data — keeps the UI honest about what events have happened
10
+ * (instead of showing filter options that never match).
11
+ */
12
+ interface AuditLogRow {
13
+ ID: string;
14
+ EventDate: Date;
15
+ UserID: string;
16
+ UserName: string;
17
+ AuditLogTypeID: string;
18
+ EventType: string;
19
+ Description: string;
20
+ Details: string | null;
21
+ }
22
+ export declare class ListAuditLogComponent extends BaseAngularComponent implements OnInit {
23
+ private readonly cdr;
24
+ /** ListID we're auditing. Required. */
25
+ get ListID(): string | null;
26
+ set ListID(value: string | null);
27
+ private _listId;
28
+ /** Max rows to load. Defaults to a sensible 200 for the per-list view. */
29
+ PageSize: number;
30
+ entries: AuditLogRow[];
31
+ eventTypes: Array<{
32
+ ID: string;
33
+ Name: string;
34
+ }>;
35
+ filterType: string;
36
+ filterText: string;
37
+ loading: boolean;
38
+ errorMessage: string | null;
39
+ private initialized;
40
+ ngOnInit(): Promise<void>;
41
+ get visibleEntries(): AuditLogRow[];
42
+ OnFilterChange(): void;
43
+ formatDate(d: Date): string;
44
+ formatDetails(raw: string | null): string;
45
+ /**
46
+ * Bulk-load audit-log entries + their type names + user names in three
47
+ * RunViews via the batch helper so the UI only spins once. We then
48
+ * compose the joined view in memory (1k rows × 3 lookups stays cheap).
49
+ */
50
+ private loadEntries;
51
+ static ɵfac: i0.ɵɵFactoryDeclaration<ListAuditLogComponent, never>;
52
+ static ɵcmp: i0.ɵɵComponentDeclaration<ListAuditLogComponent, "mj-list-audit-log", never, { "ListID": { "alias": "ListID"; "required": false; }; "PageSize": { "alias": "PageSize"; "required": false; }; }, {}, never, never, false, never>;
53
+ }
54
+ export {};
55
+ //# sourceMappingURL=list-audit-log.component.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list-audit-log.component.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/list-audit-log/list-audit-log.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgE,MAAM,EAAU,MAAM,eAAe,CAAC;AAE7G,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;;AAGrE;;;;;;;GAOG;AAEH,UAAU,WAAW;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,IAAI,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,qBAOa,qBAAsB,SAAQ,oBAAqB,YAAW,MAAM;IAC/E,OAAO,CAAC,QAAQ,CAAC,GAAG,CAA6B;IAEjD,uCAAuC;IACvC,IACI,MAAM,IAAI,MAAM,GAAG,IAAI,CAE1B;IACD,IAAI,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAO9B;IACD,OAAO,CAAC,OAAO,CAAuB;IAEtC,0EAA0E;IACjE,QAAQ,SAAO;IAEjB,OAAO,EAAE,WAAW,EAAE,CAAM;IAC5B,UAAU,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAM;IACrD,UAAU,SAAM;IAChB,UAAU,SAAM;IAChB,OAAO,UAAS;IAChB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAQ;IAE1C,OAAO,CAAC,WAAW,CAAS;IAEtB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAK/B,IAAW,cAAc,IAAI,WAAW,EAAE,CAUzC;IAEM,cAAc,IAAI,IAAI;IAItB,UAAU,CAAC,CAAC,EAAE,IAAI,GAAG,MAAM;IAI3B,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM;IAUhD;;;;OAIG;YACW,WAAW;yCAtEd,qBAAqB;2CAArB,qBAAqB;CA2JjC"}
@@ -0,0 +1,280 @@
1
+ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, inject } from '@angular/core';
2
+ import { BaseAngularComponent } from '@memberjunction/ng-base-types';
3
+ import { RunView } from '@memberjunction/core';
4
+ import * as i0 from "@angular/core";
5
+ import * as i1 from "@angular/forms";
6
+ import * as i2 from "@memberjunction/ng-shared-generic";
7
+ const _forTrack0 = ($index, $item) => $item.ID;
8
+ function ListAuditLogComponent_For_9_Template(rf, ctx) { if (rf & 1) {
9
+ i0.ɵɵelementStart(0, "option", 7);
10
+ i0.ɵɵtext(1);
11
+ i0.ɵɵelementEnd();
12
+ } if (rf & 2) {
13
+ const t_r1 = ctx.$implicit;
14
+ i0.ɵɵproperty("value", t_r1.ID);
15
+ i0.ɵɵadvance();
16
+ i0.ɵɵtextInterpolate(t_r1.Name);
17
+ } }
18
+ function ListAuditLogComponent_Conditional_10_Template(rf, ctx) { if (rf & 1) {
19
+ i0.ɵɵelementStart(0, "div", 8);
20
+ i0.ɵɵelement(1, "mj-loading", 12);
21
+ i0.ɵɵelementEnd();
22
+ } }
23
+ function ListAuditLogComponent_Conditional_11_Template(rf, ctx) { if (rf & 1) {
24
+ i0.ɵɵelementStart(0, "div", 9);
25
+ i0.ɵɵelement(1, "i", 13);
26
+ i0.ɵɵtext(2);
27
+ i0.ɵɵelementEnd();
28
+ } if (rf & 2) {
29
+ const ctx_r1 = i0.ɵɵnextContext();
30
+ i0.ɵɵadvance(2);
31
+ i0.ɵɵtextInterpolate1(" ", ctx_r1.errorMessage, " ");
32
+ } }
33
+ function ListAuditLogComponent_Conditional_12_Template(rf, ctx) { if (rf & 1) {
34
+ i0.ɵɵelementStart(0, "div", 10);
35
+ i0.ɵɵelement(1, "i", 14);
36
+ i0.ɵɵelementStart(2, "span");
37
+ i0.ɵɵtext(3, "No audit log entries match the current filters.");
38
+ i0.ɵɵelementEnd()();
39
+ } }
40
+ function ListAuditLogComponent_Conditional_13_For_13_Conditional_10_Template(rf, ctx) { if (rf & 1) {
41
+ i0.ɵɵelementStart(0, "details", 19)(1, "summary");
42
+ i0.ɵɵtext(2, "details");
43
+ i0.ɵɵelementEnd();
44
+ i0.ɵɵelementStart(3, "pre");
45
+ i0.ɵɵtext(4);
46
+ i0.ɵɵelementEnd()();
47
+ } if (rf & 2) {
48
+ const entry_r3 = i0.ɵɵnextContext().$implicit;
49
+ const ctx_r1 = i0.ɵɵnextContext(2);
50
+ i0.ɵɵadvance(4);
51
+ i0.ɵɵtextInterpolate(ctx_r1.formatDetails(entry_r3.Details));
52
+ } }
53
+ function ListAuditLogComponent_Conditional_13_For_13_Template(rf, ctx) { if (rf & 1) {
54
+ i0.ɵɵelementStart(0, "tr")(1, "td", 15);
55
+ i0.ɵɵtext(2);
56
+ i0.ɵɵelementEnd();
57
+ i0.ɵɵelementStart(3, "td", 16);
58
+ i0.ɵɵtext(4);
59
+ i0.ɵɵelementEnd();
60
+ i0.ɵɵelementStart(5, "td")(6, "span", 17);
61
+ i0.ɵɵtext(7);
62
+ i0.ɵɵelementEnd()();
63
+ i0.ɵɵelementStart(8, "td", 18);
64
+ i0.ɵɵtext(9);
65
+ i0.ɵɵconditionalCreate(10, ListAuditLogComponent_Conditional_13_For_13_Conditional_10_Template, 5, 1, "details", 19);
66
+ i0.ɵɵelementEnd()();
67
+ } if (rf & 2) {
68
+ const entry_r3 = ctx.$implicit;
69
+ const ctx_r1 = i0.ɵɵnextContext(2);
70
+ i0.ɵɵadvance(2);
71
+ i0.ɵɵtextInterpolate(ctx_r1.formatDate(entry_r3.EventDate));
72
+ i0.ɵɵadvance(2);
73
+ i0.ɵɵtextInterpolate(entry_r3.UserName);
74
+ i0.ɵɵadvance(3);
75
+ i0.ɵɵtextInterpolate(entry_r3.EventType);
76
+ i0.ɵɵadvance(2);
77
+ i0.ɵɵtextInterpolate1(" ", entry_r3.Description, " ");
78
+ i0.ɵɵadvance();
79
+ i0.ɵɵconditional(entry_r3.Details ? 10 : -1);
80
+ } }
81
+ function ListAuditLogComponent_Conditional_13_Template(rf, ctx) { if (rf & 1) {
82
+ i0.ɵɵelementStart(0, "table", 11)(1, "thead")(2, "tr")(3, "th");
83
+ i0.ɵɵtext(4, "When");
84
+ i0.ɵɵelementEnd();
85
+ i0.ɵɵelementStart(5, "th");
86
+ i0.ɵɵtext(6, "Who");
87
+ i0.ɵɵelementEnd();
88
+ i0.ɵɵelementStart(7, "th");
89
+ i0.ɵɵtext(8, "Event");
90
+ i0.ɵɵelementEnd();
91
+ i0.ɵɵelementStart(9, "th");
92
+ i0.ɵɵtext(10, "Description");
93
+ i0.ɵɵelementEnd()()();
94
+ i0.ɵɵelementStart(11, "tbody");
95
+ i0.ɵɵrepeaterCreate(12, ListAuditLogComponent_Conditional_13_For_13_Template, 11, 5, "tr", null, _forTrack0);
96
+ i0.ɵɵelementEnd()();
97
+ } if (rf & 2) {
98
+ const ctx_r1 = i0.ɵɵnextContext();
99
+ i0.ɵɵadvance(12);
100
+ i0.ɵɵrepeater(ctx_r1.visibleEntries);
101
+ } }
102
+ export class ListAuditLogComponent extends BaseAngularComponent {
103
+ cdr = inject(ChangeDetectorRef);
104
+ /** ListID we're auditing. Required. */
105
+ get ListID() {
106
+ return this._listId;
107
+ }
108
+ set ListID(value) {
109
+ if (this._listId !== value) {
110
+ this._listId = value;
111
+ if (value && this.initialized) {
112
+ void this.loadEntries();
113
+ }
114
+ }
115
+ }
116
+ _listId = null;
117
+ /** Max rows to load. Defaults to a sensible 200 for the per-list view. */
118
+ PageSize = 200;
119
+ entries = [];
120
+ eventTypes = [];
121
+ filterType = '';
122
+ filterText = '';
123
+ loading = false;
124
+ errorMessage = null;
125
+ initialized = false;
126
+ async ngOnInit() {
127
+ this.initialized = true;
128
+ if (this._listId)
129
+ await this.loadEntries();
130
+ }
131
+ get visibleEntries() {
132
+ const term = this.filterText.trim().toLowerCase();
133
+ return this.entries.filter((e) => {
134
+ if (this.filterType && e.AuditLogTypeID !== this.filterType)
135
+ return false;
136
+ if (term) {
137
+ const hay = `${e.Description} ${e.UserName}`.toLowerCase();
138
+ if (!hay.includes(term))
139
+ return false;
140
+ }
141
+ return true;
142
+ });
143
+ }
144
+ OnFilterChange() {
145
+ this.cdr.markForCheck();
146
+ }
147
+ formatDate(d) {
148
+ return d.toLocaleString();
149
+ }
150
+ formatDetails(raw) {
151
+ if (!raw)
152
+ return '';
153
+ try {
154
+ const parsed = JSON.parse(raw);
155
+ return JSON.stringify(parsed, null, 2);
156
+ }
157
+ catch {
158
+ return raw;
159
+ }
160
+ }
161
+ /**
162
+ * Bulk-load audit-log entries + their type names + user names in three
163
+ * RunViews via the batch helper so the UI only spins once. We then
164
+ * compose the joined view in memory (1k rows × 3 lookups stays cheap).
165
+ */
166
+ async loadEntries() {
167
+ if (!this._listId)
168
+ return;
169
+ this.loading = true;
170
+ this.errorMessage = null;
171
+ this.cdr.markForCheck();
172
+ try {
173
+ const md = this.ProviderToUse;
174
+ const listsEntity = md.Entities.find((e) => e.Name === 'MJ: Lists');
175
+ if (!listsEntity)
176
+ throw new Error('MJ: Lists entity not found in metadata');
177
+ const rv = RunView.FromMetadataProvider(md);
178
+ const logs = await rv.RunView({
179
+ EntityName: 'MJ: Audit Logs',
180
+ ExtraFilter: `EntityID='${listsEntity.ID}' AND RecordID='${this._listId.replace(/'/g, "''")}'`,
181
+ OrderBy: '__mj_CreatedAt DESC',
182
+ MaxRows: this.PageSize,
183
+ Fields: ['ID', 'UserID', 'AuditLogTypeID', 'Description', 'Details', '__mj_CreatedAt'],
184
+ ResultType: 'simple',
185
+ });
186
+ if (!logs.Success) {
187
+ this.errorMessage = logs.ErrorMessage ?? 'Failed to load audit logs';
188
+ this.entries = [];
189
+ return;
190
+ }
191
+ const rows = logs.Results ?? [];
192
+ const userIds = Array.from(new Set(rows.map((r) => String(r.UserID))));
193
+ const typeIds = Array.from(new Set(rows.map((r) => String(r.AuditLogTypeID))));
194
+ const [users, types] = await Promise.all([
195
+ userIds.length > 0
196
+ ? rv.RunView({
197
+ EntityName: 'MJ: Users',
198
+ ExtraFilter: `ID IN (${userIds.map((id) => `'${id}'`).join(',')})`,
199
+ Fields: ['ID', 'Name'],
200
+ ResultType: 'simple',
201
+ })
202
+ : Promise.resolve({ Success: true, Results: [] }),
203
+ typeIds.length > 0
204
+ ? rv.RunView({
205
+ EntityName: 'MJ: Audit Log Types',
206
+ ExtraFilter: `ID IN (${typeIds.map((id) => `'${id}'`).join(',')})`,
207
+ Fields: ['ID', 'Name'],
208
+ ResultType: 'simple',
209
+ })
210
+ : Promise.resolve({ Success: true, Results: [] }),
211
+ ]);
212
+ const userById = new Map();
213
+ for (const u of users.Results ?? [])
214
+ userById.set(String(u.ID), String(u.Name));
215
+ const typeById = new Map();
216
+ for (const t of types.Results ?? [])
217
+ typeById.set(String(t.ID), String(t.Name));
218
+ this.entries = rows.map((r) => ({
219
+ ID: String(r.ID),
220
+ EventDate: new Date(r.__mj_CreatedAt),
221
+ UserID: String(r.UserID),
222
+ UserName: userById.get(String(r.UserID)) ?? '(unknown user)',
223
+ AuditLogTypeID: String(r.AuditLogTypeID),
224
+ EventType: typeById.get(String(r.AuditLogTypeID)) ?? '(unknown event)',
225
+ Description: r.Description ?? '',
226
+ Details: r.Details,
227
+ }));
228
+ // Populate the event-type dropdown with only types we actually have.
229
+ this.eventTypes = [...typeById.entries()]
230
+ .map(([ID, Name]) => ({ ID, Name }))
231
+ .sort((a, b) => a.Name.localeCompare(b.Name));
232
+ }
233
+ catch (e) {
234
+ this.errorMessage = e instanceof Error ? e.message : String(e);
235
+ this.entries = [];
236
+ }
237
+ finally {
238
+ this.loading = false;
239
+ this.cdr.markForCheck();
240
+ }
241
+ }
242
+ static ɵfac = /*@__PURE__*/ (() => { let ɵListAuditLogComponent_BaseFactory; return function ListAuditLogComponent_Factory(__ngFactoryType__) { return (ɵListAuditLogComponent_BaseFactory || (ɵListAuditLogComponent_BaseFactory = i0.ɵɵgetInheritedFactory(ListAuditLogComponent)))(__ngFactoryType__ || ListAuditLogComponent); }; })();
243
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: ListAuditLogComponent, selectors: [["mj-list-audit-log"]], inputs: { ListID: "ListID", PageSize: "PageSize" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 14, vars: 3, consts: [[1, "audit-log"], [1, "audit-log__toolbar"], [1, "audit-search"], [1, "fa-solid", "fa-search"], ["type", "text", "placeholder", "Filter by description or user\u2026", 1, "mj-input", 3, "ngModelChange", "ngModel"], ["title", "Filter by event type", 1, "mj-input", "mj-select", 3, "ngModelChange", "ngModel"], ["value", ""], [3, "value"], [1, "audit-state"], [1, "audit-state", "audit-state--error"], [1, "audit-state", "audit-state--empty"], [1, "audit-table"], ["text", "Loading audit log...", "size", "medium"], [1, "fa-solid", "fa-triangle-exclamation"], [1, "fa-solid", "fa-clock-rotate-left"], [1, "audit-cell-date"], [1, "audit-cell-user"], [1, "event-chip"], [1, "audit-cell-description"], [1, "details-toggle"]], template: function ListAuditLogComponent_Template(rf, ctx) { if (rf & 1) {
244
+ i0.ɵɵelementStart(0, "div", 0)(1, "div", 1)(2, "div", 2);
245
+ i0.ɵɵelement(3, "i", 3);
246
+ i0.ɵɵelementStart(4, "input", 4);
247
+ i0.ɵɵtwoWayListener("ngModelChange", function ListAuditLogComponent_Template_input_ngModelChange_4_listener($event) { i0.ɵɵtwoWayBindingSet(ctx.filterText, $event) || (ctx.filterText = $event); return $event; });
248
+ i0.ɵɵlistener("ngModelChange", function ListAuditLogComponent_Template_input_ngModelChange_4_listener() { return ctx.OnFilterChange(); });
249
+ i0.ɵɵelementEnd()();
250
+ i0.ɵɵelementStart(5, "select", 5);
251
+ i0.ɵɵtwoWayListener("ngModelChange", function ListAuditLogComponent_Template_select_ngModelChange_5_listener($event) { i0.ɵɵtwoWayBindingSet(ctx.filterType, $event) || (ctx.filterType = $event); return $event; });
252
+ i0.ɵɵlistener("ngModelChange", function ListAuditLogComponent_Template_select_ngModelChange_5_listener() { return ctx.OnFilterChange(); });
253
+ i0.ɵɵelementStart(6, "option", 6);
254
+ i0.ɵɵtext(7, "All Events");
255
+ i0.ɵɵelementEnd();
256
+ i0.ɵɵrepeaterCreate(8, ListAuditLogComponent_For_9_Template, 2, 2, "option", 7, _forTrack0);
257
+ i0.ɵɵelementEnd()();
258
+ i0.ɵɵconditionalCreate(10, ListAuditLogComponent_Conditional_10_Template, 2, 0, "div", 8)(11, ListAuditLogComponent_Conditional_11_Template, 3, 1, "div", 9)(12, ListAuditLogComponent_Conditional_12_Template, 4, 0, "div", 10)(13, ListAuditLogComponent_Conditional_13_Template, 14, 0, "table", 11);
259
+ i0.ɵɵelementEnd();
260
+ } if (rf & 2) {
261
+ i0.ɵɵadvance(4);
262
+ i0.ɵɵtwoWayProperty("ngModel", ctx.filterText);
263
+ i0.ɵɵadvance();
264
+ i0.ɵɵtwoWayProperty("ngModel", ctx.filterType);
265
+ i0.ɵɵadvance(3);
266
+ i0.ɵɵrepeater(ctx.eventTypes);
267
+ i0.ɵɵadvance(2);
268
+ i0.ɵɵconditional(ctx.loading ? 10 : ctx.errorMessage ? 11 : ctx.visibleEntries.length === 0 ? 12 : 13);
269
+ } }, dependencies: [i1.NgSelectOption, i1.ɵNgSelectMultipleOption, i1.DefaultValueAccessor, i1.SelectControlValueAccessor, i1.NgControlStatus, i1.NgModel, i2.LoadingComponent], styles: [".audit-log[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 16px;\n}\n\n.audit-log__toolbar[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.audit-search[_ngcontent-%COMP%] {\n flex: 1;\n max-width: 360px;\n position: relative;\n display: flex;\n align-items: center;\n}\n\n.audit-search[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n position: absolute;\n left: 10px;\n color: var(--mj-text-muted);\n pointer-events: none;\n}\n\n.audit-search[_ngcontent-%COMP%] input[_ngcontent-%COMP%] {\n width: 100%;\n padding-left: 32px;\n}\n\n.audit-state[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 8px;\n padding: 32px;\n color: var(--mj-text-secondary);\n font-size: 13px;\n}\n\n.audit-state--error[_ngcontent-%COMP%] {\n color: var(--mj-status-error-text);\n background: var(--mj-status-error-bg);\n border: 1px solid var(--mj-status-error-border);\n border-radius: 8px;\n}\n\n.audit-state[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 24px;\n color: var(--mj-text-muted);\n}\n\n.audit-table[_ngcontent-%COMP%] {\n width: 100%;\n border-collapse: collapse;\n font-size: 13px;\n}\n\n.audit-table[_ngcontent-%COMP%] th[_ngcontent-%COMP%] {\n text-align: left;\n font-size: 11px;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n color: var(--mj-text-muted);\n padding: 8px 10px;\n border-bottom: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-card);\n}\n\n.audit-table[_ngcontent-%COMP%] td[_ngcontent-%COMP%] {\n padding: 10px;\n border-bottom: 1px solid var(--mj-border-subtle);\n vertical-align: top;\n color: var(--mj-text-primary);\n}\n\n.audit-cell-date[_ngcontent-%COMP%] {\n white-space: nowrap;\n color: var(--mj-text-secondary);\n font-size: 12.5px;\n}\n\n.audit-cell-user[_ngcontent-%COMP%] {\n white-space: nowrap;\n}\n\n.event-chip[_ngcontent-%COMP%] {\n display: inline-block;\n font-size: 11px;\n padding: 2px 8px;\n border-radius: 999px;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-weight: 600;\n white-space: nowrap;\n}\n\n.audit-cell-description[_ngcontent-%COMP%] {\n font-size: 12.5px;\n color: var(--mj-text-secondary);\n line-height: 1.45;\n}\n\n.details-toggle[_ngcontent-%COMP%] {\n margin-top: 4px;\n font-size: 11.5px;\n}\n\n.details-toggle[_ngcontent-%COMP%] summary[_ngcontent-%COMP%] {\n cursor: pointer;\n color: var(--mj-text-link);\n}\n\n.details-toggle[_ngcontent-%COMP%] pre[_ngcontent-%COMP%] {\n margin: 4px 0 0;\n padding: 6px 8px;\n background: var(--mj-bg-surface-sunken);\n border-radius: 4px;\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 11px;\n color: var(--mj-text-primary);\n white-space: pre-wrap;\n}"], changeDetection: 0 });
270
+ }
271
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ListAuditLogComponent, [{
272
+ type: Component,
273
+ args: [{ standalone: false, selector: 'mj-list-audit-log', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"audit-log\">\n <div class=\"audit-log__toolbar\">\n <div class=\"audit-search\">\n <i class=\"fa-solid fa-search\"></i>\n <input\n class=\"mj-input\"\n type=\"text\"\n [(ngModel)]=\"filterText\"\n (ngModelChange)=\"OnFilterChange()\"\n placeholder=\"Filter by description or user\u2026\" />\n </div>\n <select\n class=\"mj-input mj-select\"\n [(ngModel)]=\"filterType\"\n (ngModelChange)=\"OnFilterChange()\"\n title=\"Filter by event type\">\n <option value=\"\">All Events</option>\n @for (t of eventTypes; track t.ID) {\n <option [value]=\"t.ID\">{{ t.Name }}</option>\n }\n </select>\n </div>\n\n @if (loading) {\n <div class=\"audit-state\">\n <mj-loading text=\"Loading audit log...\" size=\"medium\"></mj-loading>\n </div>\n } @else if (errorMessage) {\n <div class=\"audit-state audit-state--error\">\n <i class=\"fa-solid fa-triangle-exclamation\"></i>\n {{ errorMessage }}\n </div>\n } @else if (visibleEntries.length === 0) {\n <div class=\"audit-state audit-state--empty\">\n <i class=\"fa-solid fa-clock-rotate-left\"></i>\n <span>No audit log entries match the current filters.</span>\n </div>\n } @else {\n <table class=\"audit-table\">\n <thead>\n <tr>\n <th>When</th>\n <th>Who</th>\n <th>Event</th>\n <th>Description</th>\n </tr>\n </thead>\n <tbody>\n @for (entry of visibleEntries; track entry.ID) {\n <tr>\n <td class=\"audit-cell-date\">{{ formatDate(entry.EventDate) }}</td>\n <td class=\"audit-cell-user\">{{ entry.UserName }}</td>\n <td>\n <span class=\"event-chip\">{{ entry.EventType }}</span>\n </td>\n <td class=\"audit-cell-description\">\n {{ entry.Description }}\n @if (entry.Details) {\n <details class=\"details-toggle\">\n <summary>details</summary>\n <pre>{{ formatDetails(entry.Details) }}</pre>\n </details>\n }\n </td>\n </tr>\n }\n </tbody>\n </table>\n }\n</div>\n", styles: [".audit-log {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 16px;\n}\n\n.audit-log__toolbar {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.audit-search {\n flex: 1;\n max-width: 360px;\n position: relative;\n display: flex;\n align-items: center;\n}\n\n.audit-search i {\n position: absolute;\n left: 10px;\n color: var(--mj-text-muted);\n pointer-events: none;\n}\n\n.audit-search input {\n width: 100%;\n padding-left: 32px;\n}\n\n.audit-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 8px;\n padding: 32px;\n color: var(--mj-text-secondary);\n font-size: 13px;\n}\n\n.audit-state--error {\n color: var(--mj-status-error-text);\n background: var(--mj-status-error-bg);\n border: 1px solid var(--mj-status-error-border);\n border-radius: 8px;\n}\n\n.audit-state i {\n font-size: 24px;\n color: var(--mj-text-muted);\n}\n\n.audit-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 13px;\n}\n\n.audit-table th {\n text-align: left;\n font-size: 11px;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n color: var(--mj-text-muted);\n padding: 8px 10px;\n border-bottom: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-card);\n}\n\n.audit-table td {\n padding: 10px;\n border-bottom: 1px solid var(--mj-border-subtle);\n vertical-align: top;\n color: var(--mj-text-primary);\n}\n\n.audit-cell-date {\n white-space: nowrap;\n color: var(--mj-text-secondary);\n font-size: 12.5px;\n}\n\n.audit-cell-user {\n white-space: nowrap;\n}\n\n.event-chip {\n display: inline-block;\n font-size: 11px;\n padding: 2px 8px;\n border-radius: 999px;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-weight: 600;\n white-space: nowrap;\n}\n\n.audit-cell-description {\n font-size: 12.5px;\n color: var(--mj-text-secondary);\n line-height: 1.45;\n}\n\n.details-toggle {\n margin-top: 4px;\n font-size: 11.5px;\n}\n\n.details-toggle summary {\n cursor: pointer;\n color: var(--mj-text-link);\n}\n\n.details-toggle pre {\n margin: 4px 0 0;\n padding: 6px 8px;\n background: var(--mj-bg-surface-sunken);\n border-radius: 4px;\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 11px;\n color: var(--mj-text-primary);\n white-space: pre-wrap;\n}\n"] }]
274
+ }], null, { ListID: [{
275
+ type: Input
276
+ }], PageSize: [{
277
+ type: Input
278
+ }] }); })();
279
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(ListAuditLogComponent, { className: "ListAuditLogComponent", filePath: "src/lib/components/list-audit-log/list-audit-log.component.ts", lineNumber: 33 }); })();
280
+ //# sourceMappingURL=list-audit-log.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list-audit-log.component.js","sourceRoot":"","sources":["../../../../src/lib/components/list-audit-log/list-audit-log.component.ts","../../../../src/lib/components/list-audit-log/list-audit-log.component.html"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,SAAS,EAAE,KAAK,EAAU,MAAM,EAAE,MAAM,eAAe,CAAC;AAE7G,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;;;;;;ICevC,iCAAuB;IAAA,YAAY;IAAA,iBAAS;;;IAApC,+BAAc;IAAC,cAAY;IAAZ,+BAAY;;;IAMvC,8BAAyB;IACvB,iCAAmE;IACrE,iBAAM;;;IAEN,8BAA4C;IAC1C,wBAAgD;IAChD,YACF;IAAA,iBAAM;;;IADJ,eACF;IADE,oDACF;;;IAEA,+BAA4C;IAC1C,wBAA6C;IAC7C,4BAAM;IAAA,+DAA+C;IACvD,AADuD,iBAAO,EACxD;;;IAuBQ,AADF,mCAAgC,cACrB;IAAA,uBAAO;IAAA,iBAAU;IAC1B,2BAAK;IAAA,YAAkC;IACzC,AADyC,iBAAM,EACrC;;;;IADH,eAAkC;IAAlC,4DAAkC;;;IAV7C,AADF,0BAAI,aAC0B;IAAA,YAAiC;IAAA,iBAAK;IAClE,8BAA4B;IAAA,YAAoB;IAAA,iBAAK;IAEnD,AADF,0BAAI,eACuB;IAAA,YAAqB;IAChD,AADgD,iBAAO,EAClD;IACL,8BAAmC;IACjC,YACA;IAAA,oHAAqB;IAOzB,AADE,iBAAK,EACF;;;;IAdyB,eAAiC;IAAjC,2DAAiC;IACjC,eAAoB;IAApB,uCAAoB;IAErB,eAAqB;IAArB,wCAAqB;IAG9C,eACA;IADA,qDACA;IAAA,cAKC;IALD,4CAKC;;;IArBL,AADF,AADF,AADF,iCAA2B,YAClB,SACD,SACE;IAAA,oBAAI;IAAA,iBAAK;IACb,0BAAI;IAAA,mBAAG;IAAA,iBAAK;IACZ,0BAAI;IAAA,qBAAK;IAAA,iBAAK;IACd,0BAAI;IAAA,4BAAW;IAEnB,AADE,AADiB,iBAAK,EACjB,EACC;IACR,8BAAO;IACL,4GAiBC;IAEL,AADE,iBAAQ,EACF;;;IAnBJ,gBAiBC;IAjBD,oCAiBC;;ADjCT,MAAM,OAAO,qBAAsB,SAAQ,oBAAoB;IAC5C,GAAG,GAAG,MAAM,CAAC,iBAAiB,CAAC,CAAC;IAEjD,uCAAuC;IACvC,IACI,MAAM;QACR,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IACD,IAAI,MAAM,CAAC,KAAoB;QAC7B,IAAI,IAAI,CAAC,OAAO,KAAK,KAAK,EAAE,CAAC;YAC3B,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;YACrB,IAAI,KAAK,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;gBAC9B,KAAK,IAAI,CAAC,WAAW,EAAE,CAAC;YAC1B,CAAC;QACH,CAAC;IACH,CAAC;IACO,OAAO,GAAkB,IAAI,CAAC;IAEtC,0EAA0E;IACjE,QAAQ,GAAG,GAAG,CAAC;IAEjB,OAAO,GAAkB,EAAE,CAAC;IAC5B,UAAU,GAAwC,EAAE,CAAC;IACrD,UAAU,GAAG,EAAE,CAAC;IAChB,UAAU,GAAG,EAAE,CAAC;IAChB,OAAO,GAAG,KAAK,CAAC;IAChB,YAAY,GAAkB,IAAI,CAAC;IAElC,WAAW,GAAG,KAAK,CAAC;IAE5B,KAAK,CAAC,QAAQ;QACZ,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;QACxB,IAAI,IAAI,CAAC,OAAO;YAAE,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC;IAC7C,CAAC;IAED,IAAW,cAAc;QACvB,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAClD,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE;YAC/B,IAAI,IAAI,CAAC,UAAU,IAAI,CAAC,CAAC,cAAc,KAAK,IAAI,CAAC,UAAU;gBAAE,OAAO,KAAK,CAAC;YAC1E,IAAI,IAAI,EAAE,CAAC;gBACT,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC,WAAW,EAAE,CAAC;gBAC3D,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC;oBAAE,OAAO,KAAK,CAAC;YACxC,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC,CAAC,CAAC;IACL,CAAC;IAEM,cAAc;QACnB,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;IAC1B,CAAC;IAEM,UAAU,CAAC,CAAO;QACvB,OAAO,CAAC,CAAC,cAAc,EAAE,CAAC;IAC5B,CAAC;IAEM,aAAa,CAAC,GAAkB;QACrC,IAAI,CAAC,GAAG;YAAE,OAAO,EAAE,CAAC;QACpB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YAC/B,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QACzC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,GAAG,CAAC;QACb,CAAC;IACH,CAAC;IAED;;;;OAIG;IACK,KAAK,CAAC,WAAW;QACvB,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO;QAC1B,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;QACzB,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;QAExB,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,IAAI,CAAC,aAAa,CAAC;YAC9B,MAAM,WAAW,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC;YACpE,IAAI,CAAC,WAAW;gBAAE,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;YAE5E,MAAM,EAAE,GAAG,OAAO,CAAC,oBAAoB,CAAC,EAAE,CAAC,CAAC;YAC5C,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,OAAO,CAO1B;gBACD,UAAU,EAAE,gBAAgB;gBAC5B,WAAW,EAAE,aAAa,WAAW,CAAC,EAAE,mBAAmB,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG;gBAC9F,OAAO,EAAE,qBAAqB;gBAC9B,OAAO,EAAE,IAAI,CAAC,QAAQ;gBACtB,MAAM,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,gBAAgB,EAAE,aAAa,EAAE,SAAS,EAAE,gBAAgB,CAAC;gBACtF,UAAU,EAAE,QAAQ;aACrB,CAAC,CAAC;YAEH,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;gBAClB,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,IAAI,2BAA2B,CAAC;gBACrE,IAAI,CAAC,OAAO,GAAG,EAAE,CAAC;gBAClB,OAAO;YACT,CAAC;YAED,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC;YAChC,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;YACvE,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC;YAE/E,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;gBACvC,OAAO,CAAC,MAAM,GAAG,CAAC;oBAChB,CAAC,CAAC,EAAE,CAAC,OAAO,CAA+B;wBACvC,UAAU,EAAE,WAAW;wBACvB,WAAW,EAAE,UAAU,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG;wBAClE,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC;wBACtB,UAAU,EAAE,QAAQ;qBACrB,CAAC;oBACJ,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;gBACnD,OAAO,CAAC,MAAM,GAAG,CAAC;oBAChB,CAAC,CAAC,EAAE,CAAC,OAAO,CAA+B;wBACvC,UAAU,EAAE,qBAAqB;wBACjC,WAAW,EAAE,UAAU,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG;wBAClE,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC;wBACtB,UAAU,EAAE,QAAQ;qBACrB,CAAC;oBACJ,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;aACpD,CAAC,CAAC;YAEH,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAC;YAC3C,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,OAAO,IAAI,EAAE;gBAAE,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;YAChF,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAC;YAC3C,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,OAAO,IAAI,EAAE;gBAAE,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;YAEhF,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBAC9B,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;gBAChB,SAAS,EAAE,IAAI,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC;gBACrC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC;gBACxB,QAAQ,EAAE,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,gBAAgB;gBAC5D,cAAc,EAAE,MAAM,CAAC,CAAC,CAAC,cAAc,CAAC;gBACxC,SAAS,EAAE,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,IAAI,iBAAiB;gBACtE,WAAW,EAAE,CAAC,CAAC,WAAW,IAAI,EAAE;gBAChC,OAAO,EAAE,CAAC,CAAC,OAAO;aACnB,CAAC,CAAC,CAAC;YAEJ,qEAAqE;YACrE,IAAI,CAAC,UAAU,GAAG,CAAC,GAAG,QAAQ,CAAC,OAAO,EAAE,CAAC;iBACtC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;iBACnC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QAClD,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,IAAI,CAAC,YAAY,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;YAC/D,IAAI,CAAC,OAAO,GAAG,EAAE,CAAC;QACpB,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;YACrB,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;QAC1B,CAAC;IACH,CAAC;iQA1JU,qBAAqB,yBAArB,qBAAqB;6DAArB,qBAAqB;YC9B9B,AADF,AADF,8BAAuB,aACW,aACJ;YACxB,uBAAkC;YAClC,gCAKiD;YAF/C,mNAAwB;YACxB,iHAAiB,oBAAgB,IAAC;YAEtC,AANE,iBAKiD,EAC7C;YACN,iCAI+B;YAF7B,oNAAwB;YACxB,kHAAiB,oBAAgB,IAAC;YAElC,iCAAiB;YAAA,0BAAU;YAAA,iBAAS;YACpC,2FAEC;YAEL,AADE,iBAAS,EACL;YAgBJ,AALA,AALA,AAJF,yFAAe,mEAIY,oEAKe,uEAKjC;YAgCX,iBAAM;;YA9DE,eAAwB;YAAxB,8CAAwB;YAM1B,cAAwB;YAAxB,8CAAwB;YAIxB,eAEC;YAFD,6BAEC;YAIL,eA6CC;YA7CD,sGA6CC;;;iFDpCU,qBAAqB;cAPjC,SAAS;6BACI,KAAK,YACP,mBAAmB,mBAGZ,uBAAuB,CAAC,MAAM;;kBAM9C,KAAK;;kBAeL,KAAK;;kFAnBK,qBAAqB","sourcesContent":["import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit, inject } from '@angular/core';\n\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\nimport { RunView } from '@memberjunction/core';\n\n/**\n * Audit log view for a single List (mockup 18). Reads from the generic\n * `MJ: Audit Logs` entity, scoped to this list via `EntityID` (the MJ:\n * Lists entity row) + `RecordID` = the list ID. The event-type dropdown\n * is populated lazily from the audit-log-type rows actually present in\n * the data — keeps the UI honest about what events have happened\n * (instead of showing filter options that never match).\n */\n\ninterface AuditLogRow {\n ID: string;\n EventDate: Date;\n UserID: string;\n UserName: string;\n AuditLogTypeID: string;\n EventType: string;\n Description: string;\n Details: string | null;\n}\n\n@Component({\n standalone: false,\n selector: 'mj-list-audit-log',\n templateUrl: './list-audit-log.component.html',\n styleUrls: ['./list-audit-log.component.css'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class ListAuditLogComponent extends BaseAngularComponent implements OnInit {\n private readonly cdr = inject(ChangeDetectorRef);\n\n /** ListID we're auditing. Required. */\n @Input()\n get ListID(): string | null {\n return this._listId;\n }\n set ListID(value: string | null) {\n if (this._listId !== value) {\n this._listId = value;\n if (value && this.initialized) {\n void this.loadEntries();\n }\n }\n }\n private _listId: string | null = null;\n\n /** Max rows to load. Defaults to a sensible 200 for the per-list view. */\n @Input() PageSize = 200;\n\n public entries: AuditLogRow[] = [];\n public eventTypes: Array<{ ID: string; Name: string }> = [];\n public filterType = '';\n public filterText = '';\n public loading = false;\n public errorMessage: string | null = null;\n\n private initialized = false;\n\n async ngOnInit(): Promise<void> {\n this.initialized = true;\n if (this._listId) await this.loadEntries();\n }\n\n public get visibleEntries(): AuditLogRow[] {\n const term = this.filterText.trim().toLowerCase();\n return this.entries.filter((e) => {\n if (this.filterType && e.AuditLogTypeID !== this.filterType) return false;\n if (term) {\n const hay = `${e.Description} ${e.UserName}`.toLowerCase();\n if (!hay.includes(term)) return false;\n }\n return true;\n });\n }\n\n public OnFilterChange(): void {\n this.cdr.markForCheck();\n }\n\n public formatDate(d: Date): string {\n return d.toLocaleString();\n }\n\n public formatDetails(raw: string | null): string {\n if (!raw) return '';\n try {\n const parsed = JSON.parse(raw);\n return JSON.stringify(parsed, null, 2);\n } catch {\n return raw;\n }\n }\n\n /**\n * Bulk-load audit-log entries + their type names + user names in three\n * RunViews via the batch helper so the UI only spins once. We then\n * compose the joined view in memory (1k rows × 3 lookups stays cheap).\n */\n private async loadEntries(): Promise<void> {\n if (!this._listId) return;\n this.loading = true;\n this.errorMessage = null;\n this.cdr.markForCheck();\n\n try {\n const md = this.ProviderToUse;\n const listsEntity = md.Entities.find((e) => e.Name === 'MJ: Lists');\n if (!listsEntity) throw new Error('MJ: Lists entity not found in metadata');\n\n const rv = RunView.FromMetadataProvider(md);\n const logs = await rv.RunView<{\n ID: string;\n UserID: string;\n AuditLogTypeID: string;\n Description: string | null;\n Details: string | null;\n __mj_CreatedAt: Date;\n }>({\n EntityName: 'MJ: Audit Logs',\n ExtraFilter: `EntityID='${listsEntity.ID}' AND RecordID='${this._listId.replace(/'/g, \"''\")}'`,\n OrderBy: '__mj_CreatedAt DESC',\n MaxRows: this.PageSize,\n Fields: ['ID', 'UserID', 'AuditLogTypeID', 'Description', 'Details', '__mj_CreatedAt'],\n ResultType: 'simple',\n });\n\n if (!logs.Success) {\n this.errorMessage = logs.ErrorMessage ?? 'Failed to load audit logs';\n this.entries = [];\n return;\n }\n\n const rows = logs.Results ?? [];\n const userIds = Array.from(new Set(rows.map((r) => String(r.UserID))));\n const typeIds = Array.from(new Set(rows.map((r) => String(r.AuditLogTypeID))));\n\n const [users, types] = await Promise.all([\n userIds.length > 0\n ? rv.RunView<{ ID: string; Name: string }>({\n EntityName: 'MJ: Users',\n ExtraFilter: `ID IN (${userIds.map((id) => `'${id}'`).join(',')})`,\n Fields: ['ID', 'Name'],\n ResultType: 'simple',\n })\n : Promise.resolve({ Success: true, Results: [] }),\n typeIds.length > 0\n ? rv.RunView<{ ID: string; Name: string }>({\n EntityName: 'MJ: Audit Log Types',\n ExtraFilter: `ID IN (${typeIds.map((id) => `'${id}'`).join(',')})`,\n Fields: ['ID', 'Name'],\n ResultType: 'simple',\n })\n : Promise.resolve({ Success: true, Results: [] }),\n ]);\n\n const userById = new Map<string, string>();\n for (const u of users.Results ?? []) userById.set(String(u.ID), String(u.Name));\n const typeById = new Map<string, string>();\n for (const t of types.Results ?? []) typeById.set(String(t.ID), String(t.Name));\n\n this.entries = rows.map((r) => ({\n ID: String(r.ID),\n EventDate: new Date(r.__mj_CreatedAt),\n UserID: String(r.UserID),\n UserName: userById.get(String(r.UserID)) ?? '(unknown user)',\n AuditLogTypeID: String(r.AuditLogTypeID),\n EventType: typeById.get(String(r.AuditLogTypeID)) ?? '(unknown event)',\n Description: r.Description ?? '',\n Details: r.Details,\n }));\n\n // Populate the event-type dropdown with only types we actually have.\n this.eventTypes = [...typeById.entries()]\n .map(([ID, Name]) => ({ ID, Name }))\n .sort((a, b) => a.Name.localeCompare(b.Name));\n } catch (e) {\n this.errorMessage = e instanceof Error ? e.message : String(e);\n this.entries = [];\n } finally {\n this.loading = false;\n this.cdr.markForCheck();\n }\n }\n}\n","<div class=\"audit-log\">\n <div class=\"audit-log__toolbar\">\n <div class=\"audit-search\">\n <i class=\"fa-solid fa-search\"></i>\n <input\n class=\"mj-input\"\n type=\"text\"\n [(ngModel)]=\"filterText\"\n (ngModelChange)=\"OnFilterChange()\"\n placeholder=\"Filter by description or user…\" />\n </div>\n <select\n class=\"mj-input mj-select\"\n [(ngModel)]=\"filterType\"\n (ngModelChange)=\"OnFilterChange()\"\n title=\"Filter by event type\">\n <option value=\"\">All Events</option>\n @for (t of eventTypes; track t.ID) {\n <option [value]=\"t.ID\">{{ t.Name }}</option>\n }\n </select>\n </div>\n\n @if (loading) {\n <div class=\"audit-state\">\n <mj-loading text=\"Loading audit log...\" size=\"medium\"></mj-loading>\n </div>\n } @else if (errorMessage) {\n <div class=\"audit-state audit-state--error\">\n <i class=\"fa-solid fa-triangle-exclamation\"></i>\n {{ errorMessage }}\n </div>\n } @else if (visibleEntries.length === 0) {\n <div class=\"audit-state audit-state--empty\">\n <i class=\"fa-solid fa-clock-rotate-left\"></i>\n <span>No audit log entries match the current filters.</span>\n </div>\n } @else {\n <table class=\"audit-table\">\n <thead>\n <tr>\n <th>When</th>\n <th>Who</th>\n <th>Event</th>\n <th>Description</th>\n </tr>\n </thead>\n <tbody>\n @for (entry of visibleEntries; track entry.ID) {\n <tr>\n <td class=\"audit-cell-date\">{{ formatDate(entry.EventDate) }}</td>\n <td class=\"audit-cell-user\">{{ entry.UserName }}</td>\n <td>\n <span class=\"event-chip\">{{ entry.EventType }}</span>\n </td>\n <td class=\"audit-cell-description\">\n {{ entry.Description }}\n @if (entry.Details) {\n <details class=\"details-toggle\">\n <summary>details</summary>\n <pre>{{ formatDetails(entry.Details) }}</pre>\n </details>\n }\n </td>\n </tr>\n }\n </tbody>\n </table>\n }\n</div>\n"]}
@@ -0,0 +1,64 @@
1
+ import { EventEmitter } from '@angular/core';
2
+ import type { ListDelta } from '@memberjunction/lists-base';
3
+ import * as i0 from "@angular/core";
4
+ /**
5
+ * Confirmation dialog for any list operation that produced a `ListDelta`
6
+ * preview. This is the **only** UI path that emits a valid `DeltaToken`
7
+ * onward to `applyListDelta` — every mutating list flow funnels through
8
+ * here.
9
+ *
10
+ * Two visual states:
11
+ *
12
+ * - **Safe** (`Delta.Counts.Remove === 0`): green confirmation banner; the
13
+ * primary button is enabled immediately.
14
+ * - **Drop-warning** (`Delta.Counts.Remove > 0`): red header + warning
15
+ * banner; the primary button stays disabled until the user checks the
16
+ * acknowledgement box. The server still rejects with `DROP_NOT_CONFIRMED`
17
+ * if `ConfirmDrops` is false — this UI gate is a convenience, not the
18
+ * source of truth.
19
+ */
20
+ export declare class ListDeltaConfirmComponent {
21
+ /**
22
+ * Whether the dialog is rendered. Bound via the same getter/setter
23
+ * pattern used by the sibling share dialog so consumers can drive
24
+ * visibility from a parent state machine.
25
+ */
26
+ get Visible(): boolean;
27
+ set Visible(value: boolean);
28
+ private _visible;
29
+ /** The delta to confirm. Required — the component renders nothing useful without it. */
30
+ Delta: ListDelta | null;
31
+ /**
32
+ * Human-readable name of the target list. Optional; the dialog falls
33
+ * back to "this list" if not provided.
34
+ */
35
+ TargetListName: string | null;
36
+ /**
37
+ * Optional descriptor of the source for display ("Q4 Donor View", etc.).
38
+ * Has no functional effect — purely for the warning copy.
39
+ */
40
+ SourceLabel: string | null;
41
+ /**
42
+ * How many preview records to render in the "first N of …" lists.
43
+ * Larger deltas could otherwise blow up the DOM.
44
+ */
45
+ PreviewLimit: number;
46
+ /** Fires when the user confirms — payload is the validated delta token. */
47
+ Confirm: EventEmitter<string>;
48
+ /** Fires when the user cancels (close button, ESC, or Cancel button). */
49
+ Cancel: EventEmitter<void>;
50
+ /** Local UI state for the acknowledgement checkbox in drop-warning mode. */
51
+ dropAcknowledged: boolean;
52
+ get hasDrops(): boolean;
53
+ get canConfirm(): boolean;
54
+ get previewAdds(): string[];
55
+ get previewRemoves(): string[];
56
+ get confirmButtonLabel(): string;
57
+ get targetDisplayName(): string;
58
+ OnConfirm(): void;
59
+ OnCancel(): void;
60
+ OnAcknowledgementToggle(value: boolean): void;
61
+ static ɵfac: i0.ɵɵFactoryDeclaration<ListDeltaConfirmComponent, never>;
62
+ static ɵcmp: i0.ɵɵComponentDeclaration<ListDeltaConfirmComponent, "mj-list-delta-confirm", never, { "Visible": { "alias": "Visible"; "required": false; }; "Delta": { "alias": "Delta"; "required": false; }; "TargetListName": { "alias": "TargetListName"; "required": false; }; "SourceLabel": { "alias": "SourceLabel"; "required": false; }; "PreviewLimit": { "alias": "PreviewLimit"; "required": false; }; }, { "Confirm": "Confirm"; "Cancel": "Cancel"; }, never, never, false, never>;
63
+ }
64
+ //# sourceMappingURL=list-delta-confirm.component.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list-delta-confirm.component.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/list-delta-confirm/list-delta-confirm.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsC,YAAY,EAAiB,MAAM,eAAe,CAAC;AAEhG,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;;AAE5D;;;;;;;;;;;;;;;GAeG;AACH,qBAOa,yBAAyB;IACpC;;;;OAIG;IACH,IACI,OAAO,IAAI,OAAO,CAErB;IACD,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,EAOzB;IACD,OAAO,CAAC,QAAQ,CAAS;IAEzB,wFAAwF;IAC/E,KAAK,EAAE,SAAS,GAAG,IAAI,CAAQ;IAExC;;;OAGG;IACM,cAAc,EAAE,MAAM,GAAG,IAAI,CAAQ;IAE9C;;;OAGG;IACM,WAAW,EAAE,MAAM,GAAG,IAAI,CAAQ;IAE3C;;;OAGG;IACM,YAAY,SAAK;IAE1B,2EAA2E;IACjE,OAAO,uBAA8B;IAE/C,yEAAyE;IAC/D,MAAM,qBAA4B;IAE5C,4EAA4E;IACrE,gBAAgB,UAAS;IAEhC,IAAW,QAAQ,IAAI,OAAO,CAE7B;IAED,IAAW,UAAU,IAAI,OAAO,CAI/B;IAED,IAAW,WAAW,IAAI,MAAM,EAAE,CAEjC;IAED,IAAW,cAAc,IAAI,MAAM,EAAE,CAEpC;IAED,IAAW,kBAAkB,IAAI,MAAM,CAItC;IAED,IAAW,iBAAiB,IAAI,MAAM,CAErC;IAEM,SAAS,IAAI,IAAI;IAKjB,QAAQ,IAAI,IAAI;IAIhB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;yCAvFzC,yBAAyB;2CAAzB,yBAAyB;CA0FrC"}