@memberjunction/ng-explorer-settings 5.30.1 → 5.32.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.
- package/dist/lib/account-info/account-info.component.d.ts +2 -1
- package/dist/lib/account-info/account-info.component.d.ts.map +1 -1
- package/dist/lib/account-info/account-info.component.js +6 -5
- package/dist/lib/account-info/account-info.component.js.map +1 -1
- package/dist/lib/application-management/application-dialog/application-dialog.component.d.ts +3 -2
- package/dist/lib/application-management/application-dialog/application-dialog.component.d.ts.map +1 -1
- package/dist/lib/application-management/application-dialog/application-dialog.component.js +9 -7
- package/dist/lib/application-management/application-dialog/application-dialog.component.js.map +1 -1
- package/dist/lib/application-management/application-management.component.d.ts +1 -1
- package/dist/lib/application-management/application-management.component.d.ts.map +1 -1
- package/dist/lib/application-management/application-management.component.js +4 -4
- package/dist/lib/application-management/application-management.component.js.map +1 -1
- package/dist/lib/application-settings/application-settings.component.d.ts +2 -1
- package/dist/lib/application-settings/application-settings.component.d.ts.map +1 -1
- package/dist/lib/application-settings/application-settings.component.js +9 -7
- package/dist/lib/application-settings/application-settings.component.js.map +1 -1
- package/dist/lib/entity-permissions/entity-permissions.component.d.ts +1 -1
- package/dist/lib/entity-permissions/entity-permissions.component.d.ts.map +1 -1
- package/dist/lib/entity-permissions/entity-permissions.component.js +5 -5
- package/dist/lib/entity-permissions/entity-permissions.component.js.map +1 -1
- package/dist/lib/entity-permissions/permission-dialog/permission-dialog.component.d.ts +3 -2
- package/dist/lib/entity-permissions/permission-dialog/permission-dialog.component.d.ts.map +1 -1
- package/dist/lib/entity-permissions/permission-dialog/permission-dialog.component.js +6 -5
- package/dist/lib/entity-permissions/permission-dialog/permission-dialog.component.js.map +1 -1
- package/dist/lib/notification-preferences/notification-preferences.component.d.ts +2 -1
- package/dist/lib/notification-preferences/notification-preferences.component.d.ts.map +1 -1
- package/dist/lib/notification-preferences/notification-preferences.component.js +6 -5
- package/dist/lib/notification-preferences/notification-preferences.component.js.map +1 -1
- package/dist/lib/role-management/role-dialog/role-dialog.component.d.ts +3 -2
- package/dist/lib/role-management/role-dialog/role-dialog.component.d.ts.map +1 -1
- package/dist/lib/role-management/role-dialog/role-dialog.component.js +6 -5
- package/dist/lib/role-management/role-dialog/role-dialog.component.js.map +1 -1
- package/dist/lib/role-management/role-management.component.d.ts +1 -1
- package/dist/lib/role-management/role-management.component.d.ts.map +1 -1
- package/dist/lib/role-management/role-management.component.js +3 -3
- package/dist/lib/role-management/role-management.component.js.map +1 -1
- package/dist/lib/sql-logging/sql-logging.component.js +9 -10
- package/dist/lib/sql-logging/sql-logging.component.js.map +1 -1
- package/dist/lib/user-app-config/user-app-config.component.d.ts +2 -1
- package/dist/lib/user-app-config/user-app-config.component.d.ts.map +1 -1
- package/dist/lib/user-app-config/user-app-config.component.js +25 -21
- package/dist/lib/user-app-config/user-app-config.component.js.map +1 -1
- package/dist/lib/user-management/user-dialog/user-dialog.component.d.ts +3 -2
- package/dist/lib/user-management/user-dialog/user-dialog.component.d.ts.map +1 -1
- package/dist/lib/user-management/user-dialog/user-dialog.component.js +8 -6
- package/dist/lib/user-management/user-dialog/user-dialog.component.js.map +1 -1
- package/dist/lib/user-management/user-management.component.d.ts +1 -1
- package/dist/lib/user-management/user-management.component.d.ts.map +1 -1
- package/dist/lib/user-management/user-management.component.js +7 -7
- package/dist/lib/user-management/user-management.component.js.map +1 -1
- package/dist/lib/user-profile-settings/user-profile-settings.component.d.ts +2 -1
- package/dist/lib/user-profile-settings/user-profile-settings.component.d.ts.map +1 -1
- package/dist/lib/user-profile-settings/user-profile-settings.component.js +6 -5
- package/dist/lib/user-profile-settings/user-profile-settings.component.js.map +1 -1
- package/package.json +19 -18
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Component } from '@angular/core';
|
|
2
|
-
import { Metadata } from '@memberjunction/core';
|
|
3
2
|
import { UserInfoEngine } from '@memberjunction/core-entities';
|
|
4
3
|
import { UUIDsEqual } from '@memberjunction/global';
|
|
4
|
+
import { BaseAngularComponent } from '@memberjunction/ng-base-types';
|
|
5
5
|
import * as i0 from "@angular/core";
|
|
6
6
|
import * as i1 from "@memberjunction/ng-shared";
|
|
7
7
|
import * as i2 from "@angular/forms";
|
|
@@ -182,13 +182,14 @@ function NotificationPreferencesComponent_Conditional_2_Template(rf, ctx) { if (
|
|
|
182
182
|
* - Loads user preferences from UserInfoEngine (per-user cache)
|
|
183
183
|
* - Saves preferences using transaction groups for batch updates
|
|
184
184
|
*/
|
|
185
|
-
export class NotificationPreferencesComponent {
|
|
185
|
+
export class NotificationPreferencesComponent extends BaseAngularComponent {
|
|
186
186
|
sharedService;
|
|
187
187
|
loading = true;
|
|
188
188
|
saving = false;
|
|
189
189
|
viewModels = [];
|
|
190
190
|
hasChanges = false;
|
|
191
191
|
constructor(sharedService) {
|
|
192
|
+
super();
|
|
192
193
|
this.sharedService = sharedService;
|
|
193
194
|
}
|
|
194
195
|
/**
|
|
@@ -267,7 +268,7 @@ export class NotificationPreferencesComponent {
|
|
|
267
268
|
async save() {
|
|
268
269
|
try {
|
|
269
270
|
this.saving = true;
|
|
270
|
-
const md =
|
|
271
|
+
const md = this.ProviderToUse;
|
|
271
272
|
const currentUser = md.CurrentUser;
|
|
272
273
|
const transGroup = await md.CreateTransactionGroup();
|
|
273
274
|
// Queue all saves in transaction group - no need to await individual saves
|
|
@@ -367,7 +368,7 @@ export class NotificationPreferencesComponent {
|
|
|
367
368
|
return type.AllowUserPreference !== false;
|
|
368
369
|
}
|
|
369
370
|
static ɵfac = function NotificationPreferencesComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || NotificationPreferencesComponent)(i0.ɵɵdirectiveInject(i1.SharedService)); };
|
|
370
|
-
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: NotificationPreferencesComponent, selectors: [["mj-notification-preferences"]], standalone: false, decls: 3, vars: 1, consts: [[1, "notification-preferences-container"], ["text", "Loading notification preferences..."], [1, "content"], [1, "empty-state"], [1, "notification-types-grid"], ["role", "group", "aria-label", "Save or cancel changes", 1, "actions"], [1, "fa-solid", "fa-bell-slash"], ["role", "region", 1, "notification-type-card", 3, "changed"], ["role", "region", 1, "notification-type-card"], [1, "card-header"], [1, "icon-wrapper"], [1, "header-text"], [1, "type-description"], [1, "card-body"], [1, "preference-row"], [1, "delivery-label"], ["role", "group", 1, "delivery-checkboxes"], [1, "channel-checkbox"], ["type", "checkbox", 3, "ngModelChange", "change", "ngModel", "disabled"], ["aria-hidden", "true", 1, "fa-solid", "fa-bell"], ["aria-hidden", "true", 1, "fa-solid", "fa-envelope"], ["aria-hidden", "true", 1, "fa-solid", "fa-mobile"], ["role", "alert", "aria-live", "polite", 1, "info-message"], [1, "metadata-row"], ["aria-hidden", "true", 1, "fa-solid", "fa-info-circle"], ["aria-hidden", "true", 1, "fa-solid", "fa-clock"], [1, "metadata-text"], ["type", "button", 1, "btn", "btn-secondary", 3, "click", "disabled"], ["type", "button", 1, "btn", "btn-primary", 3, "click", "disabled"], ["aria-hidden", "true", 1, "fa-solid", "fa-spinner", "fa-spin"], ["aria-hidden", "true", 1, "fa-solid", "fa-save"]], template: function NotificationPreferencesComponent_Template(rf, ctx) { if (rf & 1) {
|
|
371
|
+
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: NotificationPreferencesComponent, selectors: [["mj-notification-preferences"]], standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 3, vars: 1, consts: [[1, "notification-preferences-container"], ["text", "Loading notification preferences..."], [1, "content"], [1, "empty-state"], [1, "notification-types-grid"], ["role", "group", "aria-label", "Save or cancel changes", 1, "actions"], [1, "fa-solid", "fa-bell-slash"], ["role", "region", 1, "notification-type-card", 3, "changed"], ["role", "region", 1, "notification-type-card"], [1, "card-header"], [1, "icon-wrapper"], [1, "header-text"], [1, "type-description"], [1, "card-body"], [1, "preference-row"], [1, "delivery-label"], ["role", "group", 1, "delivery-checkboxes"], [1, "channel-checkbox"], ["type", "checkbox", 3, "ngModelChange", "change", "ngModel", "disabled"], ["aria-hidden", "true", 1, "fa-solid", "fa-bell"], ["aria-hidden", "true", 1, "fa-solid", "fa-envelope"], ["aria-hidden", "true", 1, "fa-solid", "fa-mobile"], ["role", "alert", "aria-live", "polite", 1, "info-message"], [1, "metadata-row"], ["aria-hidden", "true", 1, "fa-solid", "fa-info-circle"], ["aria-hidden", "true", 1, "fa-solid", "fa-clock"], [1, "metadata-text"], ["type", "button", 1, "btn", "btn-secondary", 3, "click", "disabled"], ["type", "button", 1, "btn", "btn-primary", 3, "click", "disabled"], ["aria-hidden", "true", 1, "fa-solid", "fa-spinner", "fa-spin"], ["aria-hidden", "true", 1, "fa-solid", "fa-save"]], template: function NotificationPreferencesComponent_Template(rf, ctx) { if (rf & 1) {
|
|
371
372
|
i0.ɵɵelementStart(0, "div", 0);
|
|
372
373
|
i0.ɵɵconditionalCreate(1, NotificationPreferencesComponent_Conditional_1_Template, 1, 0, "mj-loading", 1)(2, NotificationPreferencesComponent_Conditional_2_Template, 4, 2, "div", 2);
|
|
373
374
|
i0.ɵɵelementEnd();
|
|
@@ -380,5 +381,5 @@ export class NotificationPreferencesComponent {
|
|
|
380
381
|
type: Component,
|
|
381
382
|
args: [{ standalone: false, selector: 'mj-notification-preferences', template: "<div class=\"notification-preferences-container\">\n @if (loading) {\n <mj-loading text=\"Loading notification preferences...\"></mj-loading>\n } @else {\n <div class=\"content\">\n @if (viewModels.length === 0) {\n <!-- Empty state -->\n <div class=\"empty-state\">\n <i class=\"fa-solid fa-bell-slash\"></i>\n <p>No notification types configured yet.</p>\n </div>\n } @else {\n <!-- Notification type cards -->\n <div class=\"notification-types-grid\">\n @for (vm of viewModels; track vm.type.ID) {\n <article class=\"notification-type-card\"\n [class.changed]=\"vm.changed\"\n role=\"region\"\n [attr.aria-label]=\"'Notification settings for ' + vm.type.Name\">\n\n <div class=\"card-header\" [style.border-left-color]=\"getTypeColor(vm.type)\">\n <div class=\"icon-wrapper\" [style.background-color]=\"getTypeColor(vm.type) + '20'\">\n <i [class]=\"'fa-solid ' + getTypeIcon(vm.type)\" [style.color]=\"getTypeColor(vm.type)\" [attr.aria-hidden]=\"true\"></i>\n </div>\n <div class=\"header-text\">\n <h4>{{ vm.type.Name }}</h4>\n <p class=\"type-description\">{{ vm.type.Description }}</p>\n </div>\n </div>\n\n <div class=\"card-body\">\n <!-- Delivery channel checkboxes -->\n <div class=\"preference-row\">\n <label class=\"delivery-label\" [attr.id]=\"'delivery-label-' + vm.type.ID\">Delivery Channels</label>\n\n <div class=\"delivery-checkboxes\" role=\"group\" [attr.aria-labelledby]=\"'delivery-label-' + vm.type.ID\">\n <label class=\"channel-checkbox\">\n <input type=\"checkbox\"\n [(ngModel)]=\"vm.inAppEnabled\"\n (change)=\"onChannelChange(vm)\"\n [disabled]=\"!getAllowUserPreference(vm.type)\"\n [attr.aria-label]=\"'Enable in-app notifications for ' + vm.type.Name\">\n <i class=\"fa-solid fa-bell\" aria-hidden=\"true\"></i>\n <span>In-App</span>\n </label>\n\n <label class=\"channel-checkbox\">\n <input type=\"checkbox\"\n [(ngModel)]=\"vm.emailEnabled\"\n (change)=\"onChannelChange(vm)\"\n [disabled]=\"!getAllowUserPreference(vm.type)\"\n [attr.aria-label]=\"'Enable email notifications for ' + vm.type.Name\">\n <i class=\"fa-solid fa-envelope\" aria-hidden=\"true\"></i>\n <span>Email</span>\n </label>\n\n <label class=\"channel-checkbox\">\n <input type=\"checkbox\"\n [(ngModel)]=\"vm.smsEnabled\"\n (change)=\"onChannelChange(vm)\"\n [disabled]=\"!getAllowUserPreference(vm.type)\"\n [attr.aria-label]=\"'Enable SMS notifications for ' + vm.type.Name\">\n <i class=\"fa-solid fa-mobile\" aria-hidden=\"true\"></i>\n <span>SMS</span>\n </label>\n </div>\n\n @if (!getAllowUserPreference(vm.type)) {\n <div class=\"info-message\" role=\"alert\" aria-live=\"polite\">\n <i class=\"fa-solid fa-info-circle\" aria-hidden=\"true\"></i>\n <span>This notification type's delivery channels cannot be customized.</span>\n </div>\n }\n </div>\n\n <!-- Auto-expire info -->\n @if (getTypeAutoExpireDays(vm.type)) {\n <div class=\"metadata-row\">\n <i class=\"fa-solid fa-clock\" aria-hidden=\"true\"></i>\n <span class=\"metadata-text\">\n Auto-marks as read after {{ getTypeAutoExpireDays(vm.type) }} day(s)\n </span>\n </div>\n }\n </div>\n </article>\n }\n </div>\n }\n\n <!-- Action buttons -->\n @if (hasChanges) {\n <div class=\"actions\" role=\"group\" aria-label=\"Save or cancel changes\">\n <button class=\"btn btn-secondary\"\n type=\"button\"\n (click)=\"cancel()\"\n [disabled]=\"saving\"\n [attr.aria-busy]=\"saving\">\n Cancel\n </button>\n <button class=\"btn btn-primary\"\n type=\"button\"\n (click)=\"save()\"\n [disabled]=\"saving\"\n [attr.aria-busy]=\"saving\">\n @if (saving) {\n <i class=\"fa-solid fa-spinner fa-spin\" aria-hidden=\"true\"></i>\n } @else {\n <i class=\"fa-solid fa-save\" aria-hidden=\"true\"></i>\n }\n <span>{{ saving ? 'Saving...' : 'Save Preferences' }}</span>\n </button>\n </div>\n }\n </div>\n }\n</div>\n", styles: ["/* =============================================================================\n Notification Preferences Component\n Using MJ Design Tokens\n ============================================================================= */\n\n/* Host element configuration */\n:host {\n display: block;\n width: 100%;\n}\n\n/* -----------------------------------------------------------------------------\n Container & Layout\n ----------------------------------------------------------------------------- */\n.notification-preferences-container {\n padding: 1.5rem;\n max-width: 1200px;\n margin: 0 auto;\n background: var(--mj-bg-page);\n}\n\n/* -----------------------------------------------------------------------------\n Loading State\n ----------------------------------------------------------------------------- */\n.content {\n animation: fadeIn 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n/* -----------------------------------------------------------------------------\n Empty State\n ----------------------------------------------------------------------------- */\n.empty-state {\n text-align: center;\n padding: 3rem 1.5rem;\n}\n\n.empty-state i {\n font-size: 4rem;\n color: var(--mj-border-strong);\n margin-bottom: 1.5rem;\n display: block;\n}\n\n.empty-state p {\n font-size: 1.125rem;\n font-weight: 500;\n color: var(--mj-text-muted);\n margin: 0;\n}\n\n/* -----------------------------------------------------------------------------\n Notification Types Grid\n ----------------------------------------------------------------------------- */\n.notification-types-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));\n gap: 1.25rem;\n margin-bottom: 1.5rem;\n}\n\n/* -----------------------------------------------------------------------------\n Notification Type Card\n ----------------------------------------------------------------------------- */\n.notification-type-card {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-strong);\n border-radius: 12px;\n border-left: 4px solid var(--mj-border-strong);\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08);\n}\n\n.notification-type-card:hover {\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1), 0 4px 8px rgba(0, 0, 0, 0.08);\n border-color: var(--mj-brand-primary);\n}\n\n.notification-type-card.changed {\n border-left-color: var(--mj-brand-primary) !important;\n border-color: var(--mj-brand-primary);\n box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1), 0 8px 16px rgba(0, 0, 0, 0.08);\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n}\n\n/* -----------------------------------------------------------------------------\n Card Header\n ----------------------------------------------------------------------------- */\n.card-header {\n display: flex;\n gap: 1rem;\n padding: 1.25rem;\n border-bottom: 1px solid var(--mj-border-strong);\n align-items: flex-start;\n}\n\n.icon-wrapper {\n width: 48px;\n height: 48px;\n border-radius: 8px;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n transition: transform 0.2s;\n}\n\n.notification-type-card:hover .icon-wrapper {\n transform: scale(1.05);\n}\n\n.icon-wrapper i {\n font-size: 1.5rem;\n}\n\n.header-text {\n flex: 1;\n min-width: 0;\n}\n\n.header-text h4 {\n margin: 0 0 0.375rem 0;\n font-size: 1.125rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.type-description {\n margin: 0;\n font-size: 0.875rem;\n color: var(--mj-text-muted);\n line-height: 1.4;\n}\n\n/* -----------------------------------------------------------------------------\n Card Body\n ----------------------------------------------------------------------------- */\n.card-body {\n padding: 1.25rem;\n}\n\n.preference-row {\n margin-bottom: 1rem;\n}\n\n.preference-row:last-child {\n margin-bottom: 0;\n}\n\n.delivery-label {\n display: block;\n font-weight: 600;\n margin-bottom: 0.75rem;\n font-size: 0.875rem;\n color: var(--mj-text-primary);\n letter-spacing: 0.01em;\n}\n\n/* -----------------------------------------------------------------------------\n Delivery Channel Checkboxes\n ----------------------------------------------------------------------------- */\n.delivery-checkboxes {\n display: flex;\n gap: 0.75rem;\n flex-wrap: wrap;\n}\n\n.channel-checkbox {\n display: flex;\n align-items: center;\n gap: 0.625rem;\n padding: 0.75rem 1rem;\n border: 2px solid var(--mj-border-strong);\n border-radius: 8px;\n cursor: pointer;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n user-select: none;\n background: var(--mj-bg-page);\n min-height: 44px;\n}\n\n.channel-checkbox input[type=\"checkbox\"] {\n width: 18px;\n height: 18px;\n cursor: pointer;\n accent-color: var(--mj-brand-primary);\n margin: 0;\n}\n\n.channel-checkbox input[type=\"checkbox\"]:disabled {\n cursor: not-allowed;\n}\n\n.channel-checkbox i {\n color: var(--mj-text-muted);\n font-size: 1rem;\n transition: color 0.2s;\n}\n\n.channel-checkbox span {\n font-size: 0.875rem;\n font-weight: 500;\n color: var(--mj-text-primary);\n}\n\n.channel-checkbox:hover:not(:has(input:disabled)) {\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n}\n\n.channel-checkbox:hover:not(:has(input:disabled)) i {\n color: var(--mj-brand-primary);\n}\n\n.channel-checkbox:has(input:checked) {\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n}\n\n.channel-checkbox:has(input:checked) i {\n color: var(--mj-brand-primary);\n}\n\n.channel-checkbox:has(input:disabled) {\n opacity: 0.5;\n cursor: not-allowed;\n background: var(--mj-bg-surface-sunken);\n}\n\n.channel-checkbox:focus-within {\n outline: 2px solid var(--mj-brand-primary);\n outline-offset: 2px;\n}\n\n/* -----------------------------------------------------------------------------\n Info Message\n ----------------------------------------------------------------------------- */\n.info-message {\n margin-top: 0.75rem;\n padding: 0.75rem;\n background: color-mix(in srgb, var(--mj-status-warning) 15%, var(--mj-bg-surface));\n border-left: 3px solid var(--mj-status-warning);\n border-radius: 8px;\n font-size: 0.8125rem;\n display: flex;\n gap: 0.75rem;\n align-items: flex-start;\n line-height: 1.4;\n color: var(--mj-text-primary);\n}\n\n.info-message i {\n color: var(--mj-status-warning);\n font-size: 1rem;\n margin-top: 0.1rem;\n flex-shrink: 0;\n}\n\n/* -----------------------------------------------------------------------------\n Metadata Row\n ----------------------------------------------------------------------------- */\n.metadata-row {\n display: flex;\n gap: 0.625rem;\n align-items: center;\n font-size: 0.8125rem;\n color: var(--mj-text-muted);\n padding-top: 0.75rem;\n margin-top: 0.75rem;\n border-top: 1px solid var(--mj-border-strong);\n}\n\n.metadata-row i {\n color: var(--mj-text-muted);\n font-size: 0.875rem;\n flex-shrink: 0;\n}\n\n.metadata-text {\n line-height: 1.4;\n}\n\n/* -----------------------------------------------------------------------------\n Action Buttons Section\n ----------------------------------------------------------------------------- */\n.actions {\n display: flex;\n justify-content: flex-start;\n gap: 0.75rem;\n padding: 1.25rem;\n background: var(--mj-bg-surface-sunken);\n border-radius: 12px;\n border: 1px solid var(--mj-border-strong);\n margin-top: 1.5rem;\n}\n\n/* -----------------------------------------------------------------------------\n Button System\n ----------------------------------------------------------------------------- */\n.btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 0.5rem;\n padding: 0.75rem 1.5rem;\n border: none;\n border-radius: 9999px;\n font-size: 0.875rem;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n white-space: nowrap;\n min-height: 44px;\n}\n\n.btn:disabled {\n opacity: 0.38;\n cursor: not-allowed;\n}\n\n.btn:focus-visible {\n outline: 2px solid var(--mj-brand-primary);\n outline-offset: 2px;\n}\n\n.btn i {\n font-size: 1rem;\n}\n\n.btn-secondary {\n background: var(--mj-bg-surface);\n color: var(--mj-brand-primary);\n border: 1px solid var(--mj-border-strong);\n}\n\n.btn-secondary:hover:not(:disabled) {\n background: var(--mj-brand-primary);\n color: var(--mj-bg-surface);\n border-color: var(--mj-brand-primary);\n}\n\n.btn-secondary:active:not(:disabled) {\n background: var(--mj-brand-primary);\n border-color: var(--mj-brand-primary);\n transform: scale(0.98);\n}\n\n.btn-primary {\n background: var(--mj-brand-primary);\n color: var(--mj-bg-surface);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08);\n}\n\n.btn-primary:hover:not(:disabled) {\n background: var(--mj-brand-primary);\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1), 0 4px 8px rgba(0, 0, 0, 0.08);\n}\n\n.btn-primary:active:not(:disabled) {\n background: var(--mj-brand-primary);\n transform: scale(0.98);\n}\n\n.btn-ghost {\n background: transparent;\n color: var(--mj-text-muted);\n border: none;\n}\n\n.btn-ghost:hover:not(:disabled) {\n background: var(--mj-brand-primary);\n color: var(--mj-bg-surface);\n}\n\n.btn-ghost:active:not(:disabled) {\n background: var(--mj-brand-primary);\n transform: scale(0.98);\n}\n\n.btn-danger {\n background: var(--mj-status-error);\n color: var(--mj-bg-surface);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08);\n}\n\n.btn-danger:hover:not(:disabled) {\n background: var(--mj-status-error);\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1), 0 4px 8px rgba(0, 0, 0, 0.08);\n}\n\n.btn-danger:active:not(:disabled) {\n background: var(--mj-status-error);\n transform: scale(0.98);\n}\n\n.btn-sm {\n padding: 0.5rem 0.875rem;\n font-size: 0.8125rem;\n min-height: 36px;\n}\n\n/* -----------------------------------------------------------------------------\n Badge System\n ----------------------------------------------------------------------------- */\n.badge-system {\n display: inline-flex;\n align-items: center;\n gap: 0.375rem;\n padding: 0.375rem 0.875rem;\n border-radius: 9999px;\n font-size: 0.75rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.03em;\n background: color-mix(in srgb, var(--mj-status-warning) 15%, var(--mj-bg-surface));\n color: var(--mj-status-warning);\n border: 1px solid var(--mj-status-warning);\n}\n\n.badge-custom {\n display: inline-flex;\n align-items: center;\n gap: 0.375rem;\n padding: 0.375rem 0.875rem;\n border-radius: 9999px;\n font-size: 0.75rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.03em;\n background: color-mix(in srgb, var(--mj-status-success) 15%, var(--mj-bg-surface));\n color: var(--mj-status-success);\n border: 1px solid var(--mj-status-success);\n}\n\n/* -----------------------------------------------------------------------------\n Accessibility: Focus Indicators\n ----------------------------------------------------------------------------- */\nbutton:focus-visible,\ninput:focus-visible {\n outline: 2px solid var(--mj-brand-primary);\n outline-offset: 2px;\n}\n\n/* -----------------------------------------------------------------------------\n Accessibility: Reduced Motion\n ----------------------------------------------------------------------------- */\n@media (prefers-reduced-motion: reduce) {\n *,\n *::before,\n *::after {\n animation-duration: 0.01ms !important;\n animation-iteration-count: 1 !important;\n transition-duration: 0.01ms !important;\n }\n}\n\n/* -----------------------------------------------------------------------------\n Animations\n ----------------------------------------------------------------------------- */\n@keyframes fadeIn {\n from {\n opacity: 0;\n transform: translateY(-8px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n/* -----------------------------------------------------------------------------\n Responsive Design - Mobile First\n ----------------------------------------------------------------------------- */\n@media (max-width: 639px) {\n .notification-preferences-container {\n padding: 1rem;\n }\n\n .notification-types-grid {\n grid-template-columns: 1fr;\n gap: 1rem;\n }\n\n .card-header {\n padding: 1rem;\n }\n\n .card-body {\n padding: 1rem;\n }\n\n .delivery-checkboxes {\n flex-direction: column;\n }\n\n .channel-checkbox {\n width: 100%;\n }\n\n .actions {\n flex-direction: column;\n padding: 1rem;\n }\n\n .actions .btn {\n width: 100%;\n justify-content: center;\n }\n}\n\n@media (min-width: 640px) and (max-width: 767px) {\n .notification-types-grid {\n grid-template-columns: 1fr;\n }\n}\n\n@media (min-width: 768px) {\n .notification-preferences-container {\n padding: 2rem;\n }\n\n .notification-types-grid {\n grid-template-columns: repeat(auto-fill, minmax(420px, 1fr));\n }\n}\n\n@media (min-width: 1024px) {\n .notification-types-grid {\n grid-template-columns: repeat(auto-fill, minmax(450px, 1fr));\n gap: 1.5rem;\n }\n}\n"] }]
|
|
382
383
|
}], () => [{ type: i1.SharedService }], null); })();
|
|
383
|
-
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(NotificationPreferencesComponent, { className: "NotificationPreferencesComponent", filePath: "src/lib/notification-preferences/notification-preferences.component.ts", lineNumber:
|
|
384
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(NotificationPreferencesComponent, { className: "NotificationPreferencesComponent", filePath: "src/lib/notification-preferences/notification-preferences.component.ts", lineNumber: 53 }); })();
|
|
384
385
|
//# sourceMappingURL=notification-preferences.component.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notification-preferences.component.js","sourceRoot":"","sources":["../../../src/lib/notification-preferences/notification-preferences.component.ts","../../../src/lib/notification-preferences/notification-preferences.component.html"],"names":[],"mappings":"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","sourcesContent":["import { Component, OnInit } from '@angular/core';\nimport { Metadata } from '@memberjunction/core';\nimport { MJUserNotificationTypeEntity, MJUserNotificationPreferenceEntity, UserInfoEngine } from '@memberjunction/core-entities';\nimport { SharedService } from '@memberjunction/ng-shared';\nimport { UUIDsEqual } from '@memberjunction/global';\n\n/**\n * View model for managing notification preferences in the UI.\n * Combines notification type definition with user's current preferences and change tracking.\n */\ninterface NotificationPreferenceViewModel {\n /** The notification type definition (read-only) */\n type: MJUserNotificationTypeEntity;\n /** User's existing preference record, or null if not yet set */\n preference: MJUserNotificationPreferenceEntity | null;\n /** Current state: In-app notifications enabled */\n inAppEnabled: boolean;\n /** Current state: Email notifications enabled */\n emailEnabled: boolean;\n /** Current state: SMS notifications enabled */\n smsEnabled: boolean;\n /** Whether this preference has been modified by the user */\n changed: boolean;\n /** Original value for rollback: In-app enabled state */\n originalInAppEnabled: boolean;\n /** Original value for rollback: Email enabled state */\n originalEmailEnabled: boolean;\n /** Original value for rollback: SMS enabled state */\n originalSmsEnabled: boolean;\n}\n\n/**\n * Component for managing user notification preferences.\n *\n * Displays notification types with configurable delivery channels (in-app, email, SMS).\n * Users can customize which channels they want to receive notifications through,\n * unless the notification type restricts user customization.\n *\n * Uses Material Design 3 (MD3) styling principles for consistent, accessible UI.\n *\n * Data flow:\n * - Loads notification types from UserInfoEngine (global cache)\n * - Loads user preferences from UserInfoEngine (per-user cache)\n * - Saves preferences using transaction groups for batch updates\n */\n@Component({\n standalone: false,\n selector: 'mj-notification-preferences',\n templateUrl: './notification-preferences.component.html',\n styleUrls: ['./notification-preferences.component.css'],\n})\nexport class NotificationPreferencesComponent implements OnInit {\n loading = true;\n saving = false;\n viewModels: NotificationPreferenceViewModel[] = [];\n hasChanges = false;\n\n constructor(private sharedService: SharedService) {}\n\n /**\n * Angular lifecycle hook - initializes the component by loading notification preferences.\n */\n async ngOnInit(): Promise<void> {\n await this.loadData();\n }\n\n /**\n * Loads notification types and user preferences from UserInfoEngine.\n * Builds view models by merging type defaults with user preferences.\n * Sorts types by Priority (ascending), then Name (alphabetical).\n * @private\n */\n private async loadData(): Promise<void> {\n try {\n this.loading = true;\n\n // UserInfoEngine is auto-configured via @RegisterForStartup()\n // NotificationEngine (server-side) loads notification types into global cache\n // UserInfoEngine provides a getter to access them from both client and server\n\n // Get notification types from UserInfoEngine, sorted client-side\n const types = [...UserInfoEngine.Instance.NotificationTypes].sort((a, b) => {\n const priorityA = a.Priority ?? 999;\n const priorityB = b.Priority ?? 999;\n if (priorityA !== priorityB) {\n return priorityA - priorityB;\n }\n return a.Name.localeCompare(b.Name);\n });\n\n // Get preferences from UserInfoEngine (entity objects - can be mutated)\n const prefs = UserInfoEngine.Instance.NotificationPreferences;\n\n // Build view models from cached data\n this.viewModels = types.map((type) => {\n const existingPref = prefs.find((p) => UUIDsEqual(p.NotificationTypeID, type.ID));\n\n // Get channel values: user preference > type default\n const inAppEnabled = existingPref?.InAppEnabled ?? type.DefaultInApp ?? true;\n const emailEnabled = existingPref?.EmailEnabled ?? type.DefaultEmail ?? false;\n const smsEnabled = existingPref?.SMSEnabled ?? type.DefaultSMS ?? false;\n\n return {\n type,\n preference: existingPref || null,\n inAppEnabled,\n emailEnabled,\n smsEnabled,\n changed: false,\n originalInAppEnabled: inAppEnabled,\n originalEmailEnabled: emailEnabled,\n originalSmsEnabled: smsEnabled,\n };\n });\n\n this.loading = false;\n } catch (error: unknown) {\n this.loading = false;\n const message = error instanceof Error ? error.message : 'Unknown error';\n this.sharedService.CreateSimpleNotification(`Failed to load notification preferences: ${message}`, 'error', 3000);\n }\n }\n\n /**\n * Called when a user toggles any delivery channel checkbox.\n * Updates the view model's changed flag by comparing current state to original values.\n * Sets the global hasChanges flag to show/hide the save/cancel buttons.\n * @param vm The view model for the notification type being modified\n */\n onChannelChange(vm: NotificationPreferenceViewModel): void {\n vm.changed = vm.inAppEnabled !== vm.originalInAppEnabled || vm.emailEnabled !== vm.originalEmailEnabled || vm.smsEnabled !== vm.originalSmsEnabled;\n this.hasChanges = this.viewModels.some((v) => v.changed);\n }\n\n /**\n * Saves all changed notification preferences using a transaction group for batch updates.\n * Creates new preference records for types that don't have existing preferences.\n * Updates the Enabled field based on whether any channel is enabled.\n * Shows success/error notification on completion.\n * @returns Promise that resolves when save is complete\n */\n async save(): Promise<void> {\n try {\n this.saving = true;\n const md = new Metadata();\n const currentUser = md.CurrentUser;\n const transGroup = await md.CreateTransactionGroup();\n\n // Queue all saves in transaction group - no need to await individual saves\n // Transaction group queues them and submits all in one batch\n for (const vm of this.viewModels.filter((v) => v.changed)) {\n let pref = vm.preference;\n\n if (!pref) {\n // Create new preference record\n pref = await md.GetEntityObject<MJUserNotificationPreferenceEntity>('MJ: User Notification Preferences');\n pref.UserID = currentUser.ID;\n pref.NotificationTypeID = vm.type.ID;\n vm.preference = pref;\n }\n\n // Set Enabled based on whether any channel is enabled\n pref.Enabled = vm.inAppEnabled || vm.emailEnabled || vm.smsEnabled;\n\n // Set the boolean channel fields\n pref.InAppEnabled = vm.inAppEnabled;\n pref.EmailEnabled = vm.emailEnabled;\n pref.SMSEnabled = vm.smsEnabled;\n pref.TransactionGroup = transGroup;\n // Don't await - Save() with transaction group queues immediately\n pref.Save();\n }\n\n // Submit transaction group - this is where the actual network call happens\n const success = await transGroup.Submit();\n\n if (success) {\n // Cache refresh happens automatically in MJUserNotificationPreferenceEntityExtended.Save()\n\n // Update original values\n this.viewModels.forEach((vm) => {\n vm.originalInAppEnabled = vm.inAppEnabled;\n vm.originalEmailEnabled = vm.emailEnabled;\n vm.originalSmsEnabled = vm.smsEnabled;\n vm.changed = false;\n });\n\n this.hasChanges = false;\n this.sharedService.CreateSimpleNotification('Notification preferences saved successfully', 'success', 2500);\n } else {\n throw new Error('Failed to save preferences');\n }\n\n this.saving = false;\n } catch (error: unknown) {\n this.saving = false;\n const message = error instanceof Error ? error.message : 'Unknown error';\n this.sharedService.CreateSimpleNotification(`Failed to save preferences: ${message}`, 'error', 3000);\n }\n }\n\n /**\n * Cancels all unsaved changes and reverts to original values.\n * Resets the changed flag on all view models and hides the action buttons.\n */\n cancel(): void {\n // Revert changes\n this.viewModels.forEach((vm) => {\n vm.inAppEnabled = vm.originalInAppEnabled;\n vm.emailEnabled = vm.originalEmailEnabled;\n vm.smsEnabled = vm.originalSmsEnabled;\n vm.changed = false;\n });\n this.hasChanges = false;\n }\n\n /**\n * Gets the Font Awesome icon class for a notification type.\n * Used for MD3 dynamic icon styling in the card header.\n * @param type The notification type entity\n * @returns Font Awesome icon class (e.g., 'fa-bell'), defaults to 'fa-bell' if not specified\n */\n getTypeIcon(type: MJUserNotificationTypeEntity): string {\n return type.Icon || 'fa-bell';\n }\n\n /**\n * Gets the color hex code for a notification type.\n * Used for MD3 dynamic color styling (icon background, border accent).\n * @param type The notification type entity\n * @returns Hex color code (e.g., '#0076B6'), defaults to '#999' if not specified\n */\n getTypeColor(type: MJUserNotificationTypeEntity): string {\n return type.Color || '#999';\n }\n\n /**\n * Gets the auto-expire duration in days for a notification type.\n * Displayed in the metadata row to inform users about automatic read marking.\n * @param type The notification type entity\n * @returns Number of days until auto-expire, or null if not configured\n */\n getTypeAutoExpireDays(type: MJUserNotificationTypeEntity): number | null {\n return type.AutoExpireDays || null;\n }\n\n /**\n * Checks if users are allowed to customize preferences for this notification type.\n * When false, the delivery channel checkboxes are disabled and an info message is shown.\n * @param type The notification type entity\n * @returns True if user customization is allowed (default), false otherwise\n */\n getAllowUserPreference(type: MJUserNotificationTypeEntity): boolean {\n return type.AllowUserPreference !== false;\n }\n}\n","<div class=\"notification-preferences-container\">\n @if (loading) {\n <mj-loading text=\"Loading notification preferences...\"></mj-loading>\n } @else {\n <div class=\"content\">\n @if (viewModels.length === 0) {\n <!-- Empty state -->\n <div class=\"empty-state\">\n <i class=\"fa-solid fa-bell-slash\"></i>\n <p>No notification types configured yet.</p>\n </div>\n } @else {\n <!-- Notification type cards -->\n <div class=\"notification-types-grid\">\n @for (vm of viewModels; track vm.type.ID) {\n <article class=\"notification-type-card\"\n [class.changed]=\"vm.changed\"\n role=\"region\"\n [attr.aria-label]=\"'Notification settings for ' + vm.type.Name\">\n\n <div class=\"card-header\" [style.border-left-color]=\"getTypeColor(vm.type)\">\n <div class=\"icon-wrapper\" [style.background-color]=\"getTypeColor(vm.type) + '20'\">\n <i [class]=\"'fa-solid ' + getTypeIcon(vm.type)\" [style.color]=\"getTypeColor(vm.type)\" [attr.aria-hidden]=\"true\"></i>\n </div>\n <div class=\"header-text\">\n <h4>{{ vm.type.Name }}</h4>\n <p class=\"type-description\">{{ vm.type.Description }}</p>\n </div>\n </div>\n\n <div class=\"card-body\">\n <!-- Delivery channel checkboxes -->\n <div class=\"preference-row\">\n <label class=\"delivery-label\" [attr.id]=\"'delivery-label-' + vm.type.ID\">Delivery Channels</label>\n\n <div class=\"delivery-checkboxes\" role=\"group\" [attr.aria-labelledby]=\"'delivery-label-' + vm.type.ID\">\n <label class=\"channel-checkbox\">\n <input type=\"checkbox\"\n [(ngModel)]=\"vm.inAppEnabled\"\n (change)=\"onChannelChange(vm)\"\n [disabled]=\"!getAllowUserPreference(vm.type)\"\n [attr.aria-label]=\"'Enable in-app notifications for ' + vm.type.Name\">\n <i class=\"fa-solid fa-bell\" aria-hidden=\"true\"></i>\n <span>In-App</span>\n </label>\n\n <label class=\"channel-checkbox\">\n <input type=\"checkbox\"\n [(ngModel)]=\"vm.emailEnabled\"\n (change)=\"onChannelChange(vm)\"\n [disabled]=\"!getAllowUserPreference(vm.type)\"\n [attr.aria-label]=\"'Enable email notifications for ' + vm.type.Name\">\n <i class=\"fa-solid fa-envelope\" aria-hidden=\"true\"></i>\n <span>Email</span>\n </label>\n\n <label class=\"channel-checkbox\">\n <input type=\"checkbox\"\n [(ngModel)]=\"vm.smsEnabled\"\n (change)=\"onChannelChange(vm)\"\n [disabled]=\"!getAllowUserPreference(vm.type)\"\n [attr.aria-label]=\"'Enable SMS notifications for ' + vm.type.Name\">\n <i class=\"fa-solid fa-mobile\" aria-hidden=\"true\"></i>\n <span>SMS</span>\n </label>\n </div>\n\n @if (!getAllowUserPreference(vm.type)) {\n <div class=\"info-message\" role=\"alert\" aria-live=\"polite\">\n <i class=\"fa-solid fa-info-circle\" aria-hidden=\"true\"></i>\n <span>This notification type's delivery channels cannot be customized.</span>\n </div>\n }\n </div>\n\n <!-- Auto-expire info -->\n @if (getTypeAutoExpireDays(vm.type)) {\n <div class=\"metadata-row\">\n <i class=\"fa-solid fa-clock\" aria-hidden=\"true\"></i>\n <span class=\"metadata-text\">\n Auto-marks as read after {{ getTypeAutoExpireDays(vm.type) }} day(s)\n </span>\n </div>\n }\n </div>\n </article>\n }\n </div>\n }\n\n <!-- Action buttons -->\n @if (hasChanges) {\n <div class=\"actions\" role=\"group\" aria-label=\"Save or cancel changes\">\n <button class=\"btn btn-secondary\"\n type=\"button\"\n (click)=\"cancel()\"\n [disabled]=\"saving\"\n [attr.aria-busy]=\"saving\">\n Cancel\n </button>\n <button class=\"btn btn-primary\"\n type=\"button\"\n (click)=\"save()\"\n [disabled]=\"saving\"\n [attr.aria-busy]=\"saving\">\n @if (saving) {\n <i class=\"fa-solid fa-spinner fa-spin\" aria-hidden=\"true\"></i>\n } @else {\n <i class=\"fa-solid fa-save\" aria-hidden=\"true\"></i>\n }\n <span>{{ saving ? 'Saving...' : 'Save Preferences' }}</span>\n </button>\n </div>\n }\n </div>\n }\n</div>\n"]}
|
|
1
|
+
{"version":3,"file":"notification-preferences.component.js","sourceRoot":"","sources":["../../../src/lib/notification-preferences/notification-preferences.component.ts","../../../src/lib/notification-preferences/notification-preferences.component.html"],"names":[],"mappings":"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","sourcesContent":["import { Component, OnInit } from '@angular/core';\nimport { Metadata } from '@memberjunction/core';\nimport { MJUserNotificationTypeEntity, MJUserNotificationPreferenceEntity, UserInfoEngine } from '@memberjunction/core-entities';\nimport { SharedService } from '@memberjunction/ng-shared';\nimport { UUIDsEqual } from '@memberjunction/global';\n\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\n/**\n * View model for managing notification preferences in the UI.\n * Combines notification type definition with user's current preferences and change tracking.\n */\ninterface NotificationPreferenceViewModel {\n /** The notification type definition (read-only) */\n type: MJUserNotificationTypeEntity;\n /** User's existing preference record, or null if not yet set */\n preference: MJUserNotificationPreferenceEntity | null;\n /** Current state: In-app notifications enabled */\n inAppEnabled: boolean;\n /** Current state: Email notifications enabled */\n emailEnabled: boolean;\n /** Current state: SMS notifications enabled */\n smsEnabled: boolean;\n /** Whether this preference has been modified by the user */\n changed: boolean;\n /** Original value for rollback: In-app enabled state */\n originalInAppEnabled: boolean;\n /** Original value for rollback: Email enabled state */\n originalEmailEnabled: boolean;\n /** Original value for rollback: SMS enabled state */\n originalSmsEnabled: boolean;\n}\n\n/**\n * Component for managing user notification preferences.\n *\n * Displays notification types with configurable delivery channels (in-app, email, SMS).\n * Users can customize which channels they want to receive notifications through,\n * unless the notification type restricts user customization.\n *\n * Uses Material Design 3 (MD3) styling principles for consistent, accessible UI.\n *\n * Data flow:\n * - Loads notification types from UserInfoEngine (global cache)\n * - Loads user preferences from UserInfoEngine (per-user cache)\n * - Saves preferences using transaction groups for batch updates\n */\n@Component({\n standalone: false,\n selector: 'mj-notification-preferences',\n templateUrl: './notification-preferences.component.html',\n styleUrls: ['./notification-preferences.component.css'],\n})\nexport class NotificationPreferencesComponent extends BaseAngularComponent implements OnInit {\n loading = true;\n saving = false;\n viewModels: NotificationPreferenceViewModel[] = [];\n hasChanges = false;\n\n constructor(private sharedService: SharedService) {\n super();}\n\n /**\n * Angular lifecycle hook - initializes the component by loading notification preferences.\n */\n async ngOnInit(): Promise<void> {\n await this.loadData();\n }\n\n /**\n * Loads notification types and user preferences from UserInfoEngine.\n * Builds view models by merging type defaults with user preferences.\n * Sorts types by Priority (ascending), then Name (alphabetical).\n * @private\n */\n private async loadData(): Promise<void> {\n try {\n this.loading = true;\n\n // UserInfoEngine is auto-configured via @RegisterForStartup()\n // NotificationEngine (server-side) loads notification types into global cache\n // UserInfoEngine provides a getter to access them from both client and server\n\n // Get notification types from UserInfoEngine, sorted client-side\n const types = [...UserInfoEngine.Instance.NotificationTypes].sort((a, b) => {\n const priorityA = a.Priority ?? 999;\n const priorityB = b.Priority ?? 999;\n if (priorityA !== priorityB) {\n return priorityA - priorityB;\n }\n return a.Name.localeCompare(b.Name);\n });\n\n // Get preferences from UserInfoEngine (entity objects - can be mutated)\n const prefs = UserInfoEngine.Instance.NotificationPreferences;\n\n // Build view models from cached data\n this.viewModels = types.map((type) => {\n const existingPref = prefs.find((p) => UUIDsEqual(p.NotificationTypeID, type.ID));\n\n // Get channel values: user preference > type default\n const inAppEnabled = existingPref?.InAppEnabled ?? type.DefaultInApp ?? true;\n const emailEnabled = existingPref?.EmailEnabled ?? type.DefaultEmail ?? false;\n const smsEnabled = existingPref?.SMSEnabled ?? type.DefaultSMS ?? false;\n\n return {\n type,\n preference: existingPref || null,\n inAppEnabled,\n emailEnabled,\n smsEnabled,\n changed: false,\n originalInAppEnabled: inAppEnabled,\n originalEmailEnabled: emailEnabled,\n originalSmsEnabled: smsEnabled,\n };\n });\n\n this.loading = false;\n } catch (error: unknown) {\n this.loading = false;\n const message = error instanceof Error ? error.message : 'Unknown error';\n this.sharedService.CreateSimpleNotification(`Failed to load notification preferences: ${message}`, 'error', 3000);\n }\n }\n\n /**\n * Called when a user toggles any delivery channel checkbox.\n * Updates the view model's changed flag by comparing current state to original values.\n * Sets the global hasChanges flag to show/hide the save/cancel buttons.\n * @param vm The view model for the notification type being modified\n */\n onChannelChange(vm: NotificationPreferenceViewModel): void {\n vm.changed = vm.inAppEnabled !== vm.originalInAppEnabled || vm.emailEnabled !== vm.originalEmailEnabled || vm.smsEnabled !== vm.originalSmsEnabled;\n this.hasChanges = this.viewModels.some((v) => v.changed);\n }\n\n /**\n * Saves all changed notification preferences using a transaction group for batch updates.\n * Creates new preference records for types that don't have existing preferences.\n * Updates the Enabled field based on whether any channel is enabled.\n * Shows success/error notification on completion.\n * @returns Promise that resolves when save is complete\n */\n async save(): Promise<void> {\n try {\n this.saving = true;\n const md = this.ProviderToUse;\n const currentUser = md.CurrentUser;\n const transGroup = await md.CreateTransactionGroup();\n\n // Queue all saves in transaction group - no need to await individual saves\n // Transaction group queues them and submits all in one batch\n for (const vm of this.viewModels.filter((v) => v.changed)) {\n let pref = vm.preference;\n\n if (!pref) {\n // Create new preference record\n pref = await md.GetEntityObject<MJUserNotificationPreferenceEntity>('MJ: User Notification Preferences');\n pref.UserID = currentUser.ID;\n pref.NotificationTypeID = vm.type.ID;\n vm.preference = pref;\n }\n\n // Set Enabled based on whether any channel is enabled\n pref.Enabled = vm.inAppEnabled || vm.emailEnabled || vm.smsEnabled;\n\n // Set the boolean channel fields\n pref.InAppEnabled = vm.inAppEnabled;\n pref.EmailEnabled = vm.emailEnabled;\n pref.SMSEnabled = vm.smsEnabled;\n pref.TransactionGroup = transGroup;\n // Don't await - Save() with transaction group queues immediately\n pref.Save();\n }\n\n // Submit transaction group - this is where the actual network call happens\n const success = await transGroup.Submit();\n\n if (success) {\n // Cache refresh happens automatically in MJUserNotificationPreferenceEntityExtended.Save()\n\n // Update original values\n this.viewModels.forEach((vm) => {\n vm.originalInAppEnabled = vm.inAppEnabled;\n vm.originalEmailEnabled = vm.emailEnabled;\n vm.originalSmsEnabled = vm.smsEnabled;\n vm.changed = false;\n });\n\n this.hasChanges = false;\n this.sharedService.CreateSimpleNotification('Notification preferences saved successfully', 'success', 2500);\n } else {\n throw new Error('Failed to save preferences');\n }\n\n this.saving = false;\n } catch (error: unknown) {\n this.saving = false;\n const message = error instanceof Error ? error.message : 'Unknown error';\n this.sharedService.CreateSimpleNotification(`Failed to save preferences: ${message}`, 'error', 3000);\n }\n }\n\n /**\n * Cancels all unsaved changes and reverts to original values.\n * Resets the changed flag on all view models and hides the action buttons.\n */\n cancel(): void {\n // Revert changes\n this.viewModels.forEach((vm) => {\n vm.inAppEnabled = vm.originalInAppEnabled;\n vm.emailEnabled = vm.originalEmailEnabled;\n vm.smsEnabled = vm.originalSmsEnabled;\n vm.changed = false;\n });\n this.hasChanges = false;\n }\n\n /**\n * Gets the Font Awesome icon class for a notification type.\n * Used for MD3 dynamic icon styling in the card header.\n * @param type The notification type entity\n * @returns Font Awesome icon class (e.g., 'fa-bell'), defaults to 'fa-bell' if not specified\n */\n getTypeIcon(type: MJUserNotificationTypeEntity): string {\n return type.Icon || 'fa-bell';\n }\n\n /**\n * Gets the color hex code for a notification type.\n * Used for MD3 dynamic color styling (icon background, border accent).\n * @param type The notification type entity\n * @returns Hex color code (e.g., '#0076B6'), defaults to '#999' if not specified\n */\n getTypeColor(type: MJUserNotificationTypeEntity): string {\n return type.Color || '#999';\n }\n\n /**\n * Gets the auto-expire duration in days for a notification type.\n * Displayed in the metadata row to inform users about automatic read marking.\n * @param type The notification type entity\n * @returns Number of days until auto-expire, or null if not configured\n */\n getTypeAutoExpireDays(type: MJUserNotificationTypeEntity): number | null {\n return type.AutoExpireDays || null;\n }\n\n /**\n * Checks if users are allowed to customize preferences for this notification type.\n * When false, the delivery channel checkboxes are disabled and an info message is shown.\n * @param type The notification type entity\n * @returns True if user customization is allowed (default), false otherwise\n */\n getAllowUserPreference(type: MJUserNotificationTypeEntity): boolean {\n return type.AllowUserPreference !== false;\n }\n}\n","<div class=\"notification-preferences-container\">\n @if (loading) {\n <mj-loading text=\"Loading notification preferences...\"></mj-loading>\n } @else {\n <div class=\"content\">\n @if (viewModels.length === 0) {\n <!-- Empty state -->\n <div class=\"empty-state\">\n <i class=\"fa-solid fa-bell-slash\"></i>\n <p>No notification types configured yet.</p>\n </div>\n } @else {\n <!-- Notification type cards -->\n <div class=\"notification-types-grid\">\n @for (vm of viewModels; track vm.type.ID) {\n <article class=\"notification-type-card\"\n [class.changed]=\"vm.changed\"\n role=\"region\"\n [attr.aria-label]=\"'Notification settings for ' + vm.type.Name\">\n\n <div class=\"card-header\" [style.border-left-color]=\"getTypeColor(vm.type)\">\n <div class=\"icon-wrapper\" [style.background-color]=\"getTypeColor(vm.type) + '20'\">\n <i [class]=\"'fa-solid ' + getTypeIcon(vm.type)\" [style.color]=\"getTypeColor(vm.type)\" [attr.aria-hidden]=\"true\"></i>\n </div>\n <div class=\"header-text\">\n <h4>{{ vm.type.Name }}</h4>\n <p class=\"type-description\">{{ vm.type.Description }}</p>\n </div>\n </div>\n\n <div class=\"card-body\">\n <!-- Delivery channel checkboxes -->\n <div class=\"preference-row\">\n <label class=\"delivery-label\" [attr.id]=\"'delivery-label-' + vm.type.ID\">Delivery Channels</label>\n\n <div class=\"delivery-checkboxes\" role=\"group\" [attr.aria-labelledby]=\"'delivery-label-' + vm.type.ID\">\n <label class=\"channel-checkbox\">\n <input type=\"checkbox\"\n [(ngModel)]=\"vm.inAppEnabled\"\n (change)=\"onChannelChange(vm)\"\n [disabled]=\"!getAllowUserPreference(vm.type)\"\n [attr.aria-label]=\"'Enable in-app notifications for ' + vm.type.Name\">\n <i class=\"fa-solid fa-bell\" aria-hidden=\"true\"></i>\n <span>In-App</span>\n </label>\n\n <label class=\"channel-checkbox\">\n <input type=\"checkbox\"\n [(ngModel)]=\"vm.emailEnabled\"\n (change)=\"onChannelChange(vm)\"\n [disabled]=\"!getAllowUserPreference(vm.type)\"\n [attr.aria-label]=\"'Enable email notifications for ' + vm.type.Name\">\n <i class=\"fa-solid fa-envelope\" aria-hidden=\"true\"></i>\n <span>Email</span>\n </label>\n\n <label class=\"channel-checkbox\">\n <input type=\"checkbox\"\n [(ngModel)]=\"vm.smsEnabled\"\n (change)=\"onChannelChange(vm)\"\n [disabled]=\"!getAllowUserPreference(vm.type)\"\n [attr.aria-label]=\"'Enable SMS notifications for ' + vm.type.Name\">\n <i class=\"fa-solid fa-mobile\" aria-hidden=\"true\"></i>\n <span>SMS</span>\n </label>\n </div>\n\n @if (!getAllowUserPreference(vm.type)) {\n <div class=\"info-message\" role=\"alert\" aria-live=\"polite\">\n <i class=\"fa-solid fa-info-circle\" aria-hidden=\"true\"></i>\n <span>This notification type's delivery channels cannot be customized.</span>\n </div>\n }\n </div>\n\n <!-- Auto-expire info -->\n @if (getTypeAutoExpireDays(vm.type)) {\n <div class=\"metadata-row\">\n <i class=\"fa-solid fa-clock\" aria-hidden=\"true\"></i>\n <span class=\"metadata-text\">\n Auto-marks as read after {{ getTypeAutoExpireDays(vm.type) }} day(s)\n </span>\n </div>\n }\n </div>\n </article>\n }\n </div>\n }\n\n <!-- Action buttons -->\n @if (hasChanges) {\n <div class=\"actions\" role=\"group\" aria-label=\"Save or cancel changes\">\n <button class=\"btn btn-secondary\"\n type=\"button\"\n (click)=\"cancel()\"\n [disabled]=\"saving\"\n [attr.aria-busy]=\"saving\">\n Cancel\n </button>\n <button class=\"btn btn-primary\"\n type=\"button\"\n (click)=\"save()\"\n [disabled]=\"saving\"\n [attr.aria-busy]=\"saving\">\n @if (saving) {\n <i class=\"fa-solid fa-spinner fa-spin\" aria-hidden=\"true\"></i>\n } @else {\n <i class=\"fa-solid fa-save\" aria-hidden=\"true\"></i>\n }\n <span>{{ saving ? 'Saving...' : 'Save Preferences' }}</span>\n </button>\n </div>\n }\n </div>\n }\n</div>\n"]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { EventEmitter, OnInit, OnDestroy, OnChanges, SimpleChanges } from '@angular/core';
|
|
2
2
|
import { FormGroup } from '@angular/forms';
|
|
3
3
|
import { MJRoleEntity } from '@memberjunction/core-entities';
|
|
4
|
+
import { BaseAngularComponent } from '@memberjunction/ng-base-types';
|
|
4
5
|
import * as i0 from "@angular/core";
|
|
5
6
|
export interface RoleDialogData {
|
|
6
7
|
role?: MJRoleEntity;
|
|
@@ -10,14 +11,14 @@ export interface RoleDialogResult {
|
|
|
10
11
|
action: 'save' | 'cancel';
|
|
11
12
|
role?: MJRoleEntity;
|
|
12
13
|
}
|
|
13
|
-
export declare class RoleDialogComponent implements OnInit, OnDestroy, OnChanges {
|
|
14
|
+
export declare class RoleDialogComponent extends BaseAngularComponent implements OnInit, OnDestroy, OnChanges {
|
|
14
15
|
data: RoleDialogData | null;
|
|
15
16
|
visible: boolean;
|
|
16
17
|
result: EventEmitter<RoleDialogResult>;
|
|
17
18
|
private fb;
|
|
18
19
|
private cdr;
|
|
19
20
|
private ngZone;
|
|
20
|
-
private metadata;
|
|
21
|
+
private get metadata();
|
|
21
22
|
roleForm: FormGroup;
|
|
22
23
|
isLoading: boolean;
|
|
23
24
|
error: string | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"role-dialog.component.d.ts","sourceRoot":"","sources":["../../../../src/lib/role-management/role-dialog/role-dialog.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,aAAa,EAAsE,MAAM,eAAe,CAAC;AAExL,OAAO,EAAiD,SAAS,EAAc,MAAM,gBAAgB,CAAC;AAEtG,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC
|
|
1
|
+
{"version":3,"file":"role-dialog.component.d.ts","sourceRoot":"","sources":["../../../../src/lib/role-management/role-dialog/role-dialog.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,aAAa,EAAsE,MAAM,eAAe,CAAC;AAExL,OAAO,EAAiD,SAAS,EAAc,MAAM,gBAAgB,CAAC;AAEtG,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAE7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;;AACrE,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC1B,IAAI,CAAC,EAAE,YAAY,CAAC;CACrB;AAED,qBAOa,mBAAoB,SAAQ,oBAAqB,YAAW,MAAM,EAAE,SAAS,EAAE,SAAS;IAC1F,IAAI,EAAE,cAAc,GAAG,IAAI,CAAQ;IACnC,OAAO,UAAS;IACf,MAAM,iCAAwC;IAExD,OAAO,CAAC,EAAE,CAAuB;IACjC,OAAO,CAAC,GAAG,CAA6B;IACxC,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,KAAK,QAAQ,GAAiC;IAC9C,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,UAAS;IAClB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAQ;;IAWnC,QAAQ,IAAI,IAAI;IAIhB,WAAW,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI;IAWzC,WAAW,IAAI,IAAI;IAInB,OAAO,CAAC,SAAS;IAUjB,WAAW,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAM/B,IAAW,WAAW,IAAI,MAAM,CAE/B;IAED,IAAW,UAAU,IAAI,OAAO,CAE/B;IAED,IAAW,YAAY,IAAI,OAAO,CAIjC;IAED,OAAO,CAAC,YAAY;IAgBP,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAsD/B,QAAQ,IAAI,IAAI;IAIvB,OAAO,CAAC,oBAAoB;yCAjJjB,mBAAmB;2CAAnB,mBAAmB;CAuJ/B"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Component, Input, Output, EventEmitter, inject, HostListener, ViewEncapsulation, ChangeDetectorRef, NgZone } from '@angular/core';
|
|
2
2
|
import { FormBuilder, Validators } from '@angular/forms';
|
|
3
|
-
import {
|
|
3
|
+
import { BaseAngularComponent } from '@memberjunction/ng-base-types';
|
|
4
4
|
import * as i0 from "@angular/core";
|
|
5
5
|
import * as i1 from "@angular/forms";
|
|
6
6
|
function RoleDialogComponent_Conditional_0_Conditional_13_Template(rf, ctx) { if (rf & 1) {
|
|
@@ -185,18 +185,19 @@ function RoleDialogComponent_Conditional_0_Template(rf, ctx) { if (rf & 1) {
|
|
|
185
185
|
i0.ɵɵadvance();
|
|
186
186
|
i0.ɵɵconditional(ctx_r1.isLoading ? 49 : 50);
|
|
187
187
|
} }
|
|
188
|
-
export class RoleDialogComponent {
|
|
188
|
+
export class RoleDialogComponent extends BaseAngularComponent {
|
|
189
189
|
data = null;
|
|
190
190
|
visible = false;
|
|
191
191
|
result = new EventEmitter();
|
|
192
192
|
fb = inject(FormBuilder);
|
|
193
193
|
cdr = inject(ChangeDetectorRef);
|
|
194
194
|
ngZone = inject(NgZone);
|
|
195
|
-
metadata
|
|
195
|
+
get metadata() { return this.ProviderToUse; }
|
|
196
196
|
roleForm;
|
|
197
197
|
isLoading = false;
|
|
198
198
|
error = null;
|
|
199
199
|
constructor() {
|
|
200
|
+
super();
|
|
200
201
|
this.roleForm = this.fb.group({
|
|
201
202
|
name: ['', [Validators.required, Validators.maxLength(50)]],
|
|
202
203
|
description: [''],
|
|
@@ -316,7 +317,7 @@ export class RoleDialogComponent {
|
|
|
316
317
|
static ɵfac = function RoleDialogComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || RoleDialogComponent)(); };
|
|
317
318
|
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: RoleDialogComponent, selectors: [["mj-role-dialog"]], hostBindings: function RoleDialogComponent_HostBindings(rf, ctx) { if (rf & 1) {
|
|
318
319
|
i0.ɵɵlistener("keydown.escape", function RoleDialogComponent_keydown_escape_HostBindingHandler($event) { return ctx.onEscapeKey($event); }, i0.ɵɵresolveDocument);
|
|
319
|
-
} }, inputs: { data: "data", visible: "visible" }, outputs: { result: "result" }, standalone: false, features: [i0.ɵɵNgOnChangesFeature], decls: 1, vars: 1, consts: [[1, "modal-backdrop"], [1, "modal-backdrop", 3, "click"], [1, "modal-dialog", 3, "click"], [1, "modal-header"], [1, "dialog-header"], [1, "dialog-title"], [1, "fa-solid", "fa-shield-halved"], [1, "dialog-subtitle"], ["type", "button", "aria-label", "Close dialog", 1, "modal-close", 3, "click"], [1, "fa-solid", "fa-times"], [1, "modal-body"], [3, "ngSubmit", "formGroup"], [1, "content-section"], [1, "form-section"], [1, "section-header"], [1, "section-title"], [1, "section-description"], [1, "form-grid"], [1, "form-field"], ["for", "name", 1, "field-label", "required"], ["id", "name", "type", "text", "formControlName", "name", "placeholder", "Sales Manager", 1, "field-input"], [1, "field-error"], ["for", "description", 1, "field-label"], ["id", "description", "formControlName", "description", "placeholder", "Describe the role's purpose and responsibilities...", "rows", "4", 1, "field-textarea"], [1, "field-hint"], [1, "fa-solid", "fa-cog"], [1, "alert", "alert-warning"], [1, "alert", "alert-info"], [1, "modal-footer"], ["type", "submit", 1, "btn", "btn-primary", 3, "disabled"], ["type", "button", 1, "btn", "btn-secondary", 3, "click"], [1, "alert", "alert-error"], [1, "fa-solid", "fa-exclamation-triangle"], [1, "fa-solid", "fa-exclamation-circle"], ["for", "directoryId", 1, "field-label"], ["id", "directoryId", "type", "text", "formControlName", "directoryId", "placeholder", "External directory identifier", 1, "field-input"], [1, "fa-solid", "fa-user-tag"], [1, "fa-solid", "fa-key"], [1, "fa-solid", "fa-info-circle"], [1, "fa-solid", "fa-spinner", "fa-spin"], [1, "fa-solid", "fa-save"]], template: function RoleDialogComponent_Template(rf, ctx) { if (rf & 1) {
|
|
320
|
+
} }, inputs: { data: "data", visible: "visible" }, outputs: { result: "result" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature, i0.ɵɵNgOnChangesFeature], decls: 1, vars: 1, consts: [[1, "modal-backdrop"], [1, "modal-backdrop", 3, "click"], [1, "modal-dialog", 3, "click"], [1, "modal-header"], [1, "dialog-header"], [1, "dialog-title"], [1, "fa-solid", "fa-shield-halved"], [1, "dialog-subtitle"], ["type", "button", "aria-label", "Close dialog", 1, "modal-close", 3, "click"], [1, "fa-solid", "fa-times"], [1, "modal-body"], [3, "ngSubmit", "formGroup"], [1, "content-section"], [1, "form-section"], [1, "section-header"], [1, "section-title"], [1, "section-description"], [1, "form-grid"], [1, "form-field"], ["for", "name", 1, "field-label", "required"], ["id", "name", "type", "text", "formControlName", "name", "placeholder", "Sales Manager", 1, "field-input"], [1, "field-error"], ["for", "description", 1, "field-label"], ["id", "description", "formControlName", "description", "placeholder", "Describe the role's purpose and responsibilities...", "rows", "4", 1, "field-textarea"], [1, "field-hint"], [1, "fa-solid", "fa-cog"], [1, "alert", "alert-warning"], [1, "alert", "alert-info"], [1, "modal-footer"], ["type", "submit", 1, "btn", "btn-primary", 3, "disabled"], ["type", "button", 1, "btn", "btn-secondary", 3, "click"], [1, "alert", "alert-error"], [1, "fa-solid", "fa-exclamation-triangle"], [1, "fa-solid", "fa-exclamation-circle"], ["for", "directoryId", 1, "field-label"], ["id", "directoryId", "type", "text", "formControlName", "directoryId", "placeholder", "External directory identifier", 1, "field-input"], [1, "fa-solid", "fa-user-tag"], [1, "fa-solid", "fa-key"], [1, "fa-solid", "fa-info-circle"], [1, "fa-solid", "fa-spinner", "fa-spin"], [1, "fa-solid", "fa-save"]], template: function RoleDialogComponent_Template(rf, ctx) { if (rf & 1) {
|
|
320
321
|
i0.ɵɵconditionalCreate(0, RoleDialogComponent_Conditional_0_Template, 54, 12, "div", 0);
|
|
321
322
|
} if (rf & 2) {
|
|
322
323
|
i0.ɵɵconditional(ctx.visible ? 0 : -1);
|
|
@@ -335,5 +336,5 @@ export class RoleDialogComponent {
|
|
|
335
336
|
type: HostListener,
|
|
336
337
|
args: ['document:keydown.escape', ['$event']]
|
|
337
338
|
}] }); })();
|
|
338
|
-
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(RoleDialogComponent, { className: "RoleDialogComponent", filePath: "src/lib/role-management/role-dialog/role-dialog.component.ts", lineNumber:
|
|
339
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(RoleDialogComponent, { className: "RoleDialogComponent", filePath: "src/lib/role-management/role-dialog/role-dialog.component.ts", lineNumber: 25 }); })();
|
|
339
340
|
//# sourceMappingURL=role-dialog.component.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"role-dialog.component.js","sourceRoot":"","sources":["../../../../src/lib/role-management/role-dialog/role-dialog.component.ts","../../../../src/lib/role-management/role-dialog/role-dialog.component.html"],"names":[],"mappings":"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","sourcesContent":["import { Component, Input, Output, EventEmitter, OnInit, OnDestroy, OnChanges, SimpleChanges, inject, HostListener, ViewEncapsulation, ChangeDetectorRef, NgZone } from '@angular/core';\n\nimport { FormsModule, ReactiveFormsModule, FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { Metadata } from '@memberjunction/core';\nimport { MJRoleEntity } from '@memberjunction/core-entities';\n\nexport interface RoleDialogData {\n role?: MJRoleEntity;\n mode: 'create' | 'edit';\n}\n\nexport interface RoleDialogResult {\n action: 'save' | 'cancel';\n role?: MJRoleEntity;\n}\n\n@Component({\n standalone: false,\n selector: 'mj-role-dialog',\n encapsulation: ViewEncapsulation.None,\n templateUrl: './role-dialog.component.html',\n styleUrls: ['./role-dialog.component.css']\n})\nexport class RoleDialogComponent implements OnInit, OnDestroy, OnChanges {\n @Input() data: RoleDialogData | null = null;\n @Input() visible = false;\n @Output() result = new EventEmitter<RoleDialogResult>();\n\n private fb = inject(FormBuilder);\n private cdr = inject(ChangeDetectorRef);\n private ngZone = inject(NgZone);\n private metadata = new Metadata();\n\n public roleForm: FormGroup;\n public isLoading = false;\n public error: string | null = null;\n\n constructor() {\n this.roleForm = this.fb.group({\n name: ['', [Validators.required, Validators.maxLength(50)]],\n description: [''],\n directoryId: ['']\n });\n }\n\n ngOnInit(): void {\n // Initial setup\n }\n\n ngOnChanges(changes: SimpleChanges): void {\n if (changes['data'] && this.data?.role && this.isEditMode) {\n this.loadRoleData();\n }\n \n // Reset form if switching to create mode or dialog becomes visible\n if (changes['visible'] && this.visible && !this.isEditMode) {\n this.resetForm();\n }\n }\n\n ngOnDestroy(): void {\n // Cleanup if needed\n }\n\n private resetForm(): void {\n this.roleForm.reset({\n name: '',\n description: '',\n directoryId: ''\n });\n this.error = null;\n }\n\n @HostListener('document:keydown.escape', ['$event'])\n onEscapeKey(event: Event): void {\n if (this.visible) {\n this.onCancel();\n }\n }\n\n public get windowTitle(): string {\n return this.isEditMode ? 'Edit Role' : 'Create New Role';\n }\n\n public get isEditMode(): boolean {\n return this.data?.mode === 'edit';\n }\n\n public get isSystemRole(): boolean {\n if (!this.data?.role) return false;\n const systemRoleNames = ['Administrator', 'User', 'Guest', 'Developer'];\n return systemRoleNames.includes(this.data.role.Name || '');\n }\n\n private loadRoleData(): void {\n if (!this.data?.role) return;\n\n const role = this.data.role;\n this.roleForm.patchValue({\n name: role.Name,\n description: role.Description,\n directoryId: role.DirectoryID\n });\n\n // Disable name editing for system roles\n if (this.isSystemRole) {\n this.roleForm.get('name')?.disable();\n }\n }\n\n public async onSubmit(): Promise<void> {\n if (this.roleForm.invalid) {\n this.markFormGroupTouched(this.roleForm);\n return;\n }\n\n this.isLoading = true;\n this.error = null;\n\n try {\n let role: MJRoleEntity;\n\n if (this.isEditMode && this.data?.role) {\n // Edit existing role\n role = this.data.role;\n } else {\n // Create new role\n role = await this.metadata.GetEntityObject<MJRoleEntity>('MJ: Roles');\n role.NewRecord();\n }\n\n // Update role properties\n const formValue = this.roleForm.value;\n \n // Only update name if not a system role\n if (!this.isSystemRole) {\n role.Name = formValue.name;\n }\n \n role.Description = formValue.description;\n role.DirectoryID = formValue.directoryId || null;\n\n // Save role\n const saveResult = await role.Save();\n if (!saveResult) {\n throw new Error(role.LatestResult?.Message || 'Failed to save role');\n }\n\n this.result.emit({ action: 'save', role });\n\n } catch (error: unknown) {\n console.error('Error saving role:', error);\n this.ngZone.run(() => {\n this.error = error instanceof Error ? error.message : 'An unexpected error occurred';\n this.cdr.markForCheck();\n });\n } finally {\n this.ngZone.run(() => {\n this.isLoading = false;\n this.cdr.markForCheck();\n });\n }\n }\n\n public onCancel(): void {\n this.result.emit({ action: 'cancel' });\n }\n\n private markFormGroupTouched(formGroup: FormGroup): void {\n Object.keys(formGroup.controls).forEach(key => {\n const control = formGroup.get(key);\n control?.markAsTouched();\n });\n }\n}","<!-- MD3 Modal Dialog -->\n@if (visible) {\n <div class=\"modal-backdrop\" (click)=\"onCancel()\">\n <div class=\"modal-dialog\" (click)=\"$event.stopPropagation()\">\n <!-- Modal Header with MJ Blue -->\n <div class=\"modal-header\">\n <div class=\"dialog-header\">\n <h2 class=\"dialog-title\">\n <i class=\"fa-solid fa-shield-halved\"></i>\n {{ isEditMode ? 'Edit Role' : 'Create New Role' }}\n </h2>\n <p class=\"dialog-subtitle\">\n {{ isEditMode ? 'Update role information and permissions' : 'Add a new role to the system' }}\n </p>\n </div>\n <button type=\"button\" class=\"modal-close\" (click)=\"onCancel()\" aria-label=\"Close dialog\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n </div>\n <!-- Modal Body -->\n <div class=\"modal-body\">\n <form [formGroup]=\"roleForm\" (ngSubmit)=\"onSubmit()\">\n @if (error) {\n <div class=\"content-section\">\n <div class=\"alert alert-error\">\n <i class=\"fa-solid fa-exclamation-triangle\"></i>\n <div>{{ error }}</div>\n </div>\n </div>\n }\n <!-- Basic Information Section -->\n <div class=\"content-section\">\n <div class=\"form-section\">\n <div class=\"section-header\">\n <h3 class=\"section-title\">\n <i class=\"fa-solid fa-shield-halved\"></i>\n Role Information\n </h3>\n <p class=\"section-description\">Define the role's basic properties and purpose</p>\n </div>\n <div class=\"form-grid\">\n <div class=\"form-field\">\n <label class=\"field-label required\" for=\"name\">Role Name</label>\n <input\n id=\"name\"\n type=\"text\"\n class=\"field-input\"\n formControlName=\"name\"\n placeholder=\"Sales Manager\"\n [class.error]=\"roleForm.get('name')?.invalid && roleForm.get('name')?.touched\"\n />\n @if (roleForm.get('name')?.invalid && roleForm.get('name')?.touched) {\n <div class=\"field-error\">\n <i class=\"fa-solid fa-exclamation-circle\"></i>\n @if (roleForm.get('name')?.errors?.['required']) {\n Role name is required\n }\n @if (roleForm.get('name')?.errors?.['maxlength']) {\n Role name cannot exceed 50 characters\n }\n </div>\n }\n </div>\n <div class=\"form-field\">\n <label class=\"field-label\" for=\"description\">Description</label>\n <textarea\n id=\"description\"\n class=\"field-textarea\"\n formControlName=\"description\"\n placeholder=\"Describe the role's purpose and responsibilities...\"\n rows=\"4\"\n ></textarea>\n <div class=\"field-hint\">\n Provide a clear description of what this role is for and what permissions it should have.\n </div>\n </div>\n @if (isEditMode) {\n <div class=\"form-field\">\n <label class=\"field-label\" for=\"directoryId\">Directory ID</label>\n <input\n id=\"directoryId\"\n type=\"text\"\n class=\"field-input\"\n formControlName=\"directoryId\"\n placeholder=\"External directory identifier\"\n />\n <div class=\"field-hint\">\n External directory identifier for syncing with Active Directory or other systems.\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n <!-- Role Type Section -->\n <div class=\"content-section\">\n <div class=\"form-section\">\n <div class=\"section-header\">\n <h3 class=\"section-title\">\n <i class=\"fa-solid fa-cog\"></i>\n Role Type\n </h3>\n <p class=\"section-description\">System or custom role classification</p>\n </div>\n <div class=\"form-grid\">\n @if (isSystemRole) {\n <div class=\"alert alert-warning\">\n <i class=\"fa-solid fa-shield-halved\"></i>\n <div>\n <strong>System Role</strong>\n <p>This is a system-defined role. Some properties may be limited for editing to maintain system integrity.</p>\n </div>\n </div>\n } @else {\n <div class=\"alert alert-info\">\n <i class=\"fa-solid fa-user-tag\"></i>\n <div>\n <strong>Custom Role</strong>\n <p>This is a custom role that can be fully configured and modified.</p>\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n <!-- Permissions Preview Section -->\n @if (isEditMode) {\n <div class=\"content-section\">\n <div class=\"form-section\">\n <div class=\"section-header\">\n <h3 class=\"section-title\">\n <i class=\"fa-solid fa-key\"></i>\n Permissions\n </h3>\n <p class=\"section-description\">Manage role permissions and access rights</p>\n </div>\n <div class=\"form-grid\">\n <div class=\"alert alert-info\">\n <i class=\"fa-solid fa-info-circle\"></i>\n <div>\n <strong>Permission Management</strong>\n <p>Role permissions can be managed in the <strong>Permissions</strong> tab of the settings dashboard. Use that interface to configure specific access rights for this role.</p>\n </div>\n </div>\n </div>\n </div>\n </div>\n }\n <!-- Modal Footer -->\n <div class=\"modal-footer\">\n <button\n type=\"submit\"\n class=\"btn btn-primary\"\n [disabled]=\"roleForm.invalid || isLoading\"\n >\n @if (isLoading) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n Saving...\n } @else {\n <i class=\"fa-solid fa-save\"></i>\n {{ isEditMode ? 'Update Role' : 'Create Role' }}\n }\n </button>\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"onCancel()\">\n <i class=\"fa-solid fa-times\"></i>\n Cancel\n </button>\n </div>\n </form>\n </div>\n </div>\n </div>\n}\n"]}
|
|
1
|
+
{"version":3,"file":"role-dialog.component.js","sourceRoot":"","sources":["../../../../src/lib/role-management/role-dialog/role-dialog.component.ts","../../../../src/lib/role-management/role-dialog/role-dialog.component.html"],"names":[],"mappings":"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","sourcesContent":["import { Component, Input, Output, EventEmitter, OnInit, OnDestroy, OnChanges, SimpleChanges, inject, HostListener, ViewEncapsulation, ChangeDetectorRef, NgZone } from '@angular/core';\n\nimport { FormsModule, ReactiveFormsModule, FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { Metadata } from '@memberjunction/core';\nimport { MJRoleEntity } from '@memberjunction/core-entities';\n\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\nexport interface RoleDialogData {\n role?: MJRoleEntity;\n mode: 'create' | 'edit';\n}\n\nexport interface RoleDialogResult {\n action: 'save' | 'cancel';\n role?: MJRoleEntity;\n}\n\n@Component({\n standalone: false,\n selector: 'mj-role-dialog',\n encapsulation: ViewEncapsulation.None,\n templateUrl: './role-dialog.component.html',\n styleUrls: ['./role-dialog.component.css']\n})\nexport class RoleDialogComponent extends BaseAngularComponent implements OnInit, OnDestroy, OnChanges {\n @Input() data: RoleDialogData | null = null;\n @Input() visible = false;\n @Output() result = new EventEmitter<RoleDialogResult>();\n\n private fb = inject(FormBuilder);\n private cdr = inject(ChangeDetectorRef);\n private ngZone = inject(NgZone);\n private get metadata() { return this.ProviderToUse; }\n public roleForm: FormGroup;\n public isLoading = false;\n public error: string | null = null;\n\n constructor() {\n super();\n this.roleForm = this.fb.group({\n name: ['', [Validators.required, Validators.maxLength(50)]],\n description: [''],\n directoryId: ['']\n });\n }\n\n ngOnInit(): void {\n // Initial setup\n }\n\n ngOnChanges(changes: SimpleChanges): void {\n if (changes['data'] && this.data?.role && this.isEditMode) {\n this.loadRoleData();\n }\n \n // Reset form if switching to create mode or dialog becomes visible\n if (changes['visible'] && this.visible && !this.isEditMode) {\n this.resetForm();\n }\n }\n\n ngOnDestroy(): void {\n // Cleanup if needed\n }\n\n private resetForm(): void {\n this.roleForm.reset({\n name: '',\n description: '',\n directoryId: ''\n });\n this.error = null;\n }\n\n @HostListener('document:keydown.escape', ['$event'])\n onEscapeKey(event: Event): void {\n if (this.visible) {\n this.onCancel();\n }\n }\n\n public get windowTitle(): string {\n return this.isEditMode ? 'Edit Role' : 'Create New Role';\n }\n\n public get isEditMode(): boolean {\n return this.data?.mode === 'edit';\n }\n\n public get isSystemRole(): boolean {\n if (!this.data?.role) return false;\n const systemRoleNames = ['Administrator', 'User', 'Guest', 'Developer'];\n return systemRoleNames.includes(this.data.role.Name || '');\n }\n\n private loadRoleData(): void {\n if (!this.data?.role) return;\n\n const role = this.data.role;\n this.roleForm.patchValue({\n name: role.Name,\n description: role.Description,\n directoryId: role.DirectoryID\n });\n\n // Disable name editing for system roles\n if (this.isSystemRole) {\n this.roleForm.get('name')?.disable();\n }\n }\n\n public async onSubmit(): Promise<void> {\n if (this.roleForm.invalid) {\n this.markFormGroupTouched(this.roleForm);\n return;\n }\n\n this.isLoading = true;\n this.error = null;\n\n try {\n let role: MJRoleEntity;\n\n if (this.isEditMode && this.data?.role) {\n // Edit existing role\n role = this.data.role;\n } else {\n // Create new role\n role = await this.metadata.GetEntityObject<MJRoleEntity>('MJ: Roles');\n role.NewRecord();\n }\n\n // Update role properties\n const formValue = this.roleForm.value;\n \n // Only update name if not a system role\n if (!this.isSystemRole) {\n role.Name = formValue.name;\n }\n \n role.Description = formValue.description;\n role.DirectoryID = formValue.directoryId || null;\n\n // Save role\n const saveResult = await role.Save();\n if (!saveResult) {\n throw new Error(role.LatestResult?.Message || 'Failed to save role');\n }\n\n this.result.emit({ action: 'save', role });\n\n } catch (error: unknown) {\n console.error('Error saving role:', error);\n this.ngZone.run(() => {\n this.error = error instanceof Error ? error.message : 'An unexpected error occurred';\n this.cdr.markForCheck();\n });\n } finally {\n this.ngZone.run(() => {\n this.isLoading = false;\n this.cdr.markForCheck();\n });\n }\n }\n\n public onCancel(): void {\n this.result.emit({ action: 'cancel' });\n }\n\n private markFormGroupTouched(formGroup: FormGroup): void {\n Object.keys(formGroup.controls).forEach(key => {\n const control = formGroup.get(key);\n control?.markAsTouched();\n });\n }\n}","<!-- MD3 Modal Dialog -->\n@if (visible) {\n <div class=\"modal-backdrop\" (click)=\"onCancel()\">\n <div class=\"modal-dialog\" (click)=\"$event.stopPropagation()\">\n <!-- Modal Header with MJ Blue -->\n <div class=\"modal-header\">\n <div class=\"dialog-header\">\n <h2 class=\"dialog-title\">\n <i class=\"fa-solid fa-shield-halved\"></i>\n {{ isEditMode ? 'Edit Role' : 'Create New Role' }}\n </h2>\n <p class=\"dialog-subtitle\">\n {{ isEditMode ? 'Update role information and permissions' : 'Add a new role to the system' }}\n </p>\n </div>\n <button type=\"button\" class=\"modal-close\" (click)=\"onCancel()\" aria-label=\"Close dialog\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n </div>\n <!-- Modal Body -->\n <div class=\"modal-body\">\n <form [formGroup]=\"roleForm\" (ngSubmit)=\"onSubmit()\">\n @if (error) {\n <div class=\"content-section\">\n <div class=\"alert alert-error\">\n <i class=\"fa-solid fa-exclamation-triangle\"></i>\n <div>{{ error }}</div>\n </div>\n </div>\n }\n <!-- Basic Information Section -->\n <div class=\"content-section\">\n <div class=\"form-section\">\n <div class=\"section-header\">\n <h3 class=\"section-title\">\n <i class=\"fa-solid fa-shield-halved\"></i>\n Role Information\n </h3>\n <p class=\"section-description\">Define the role's basic properties and purpose</p>\n </div>\n <div class=\"form-grid\">\n <div class=\"form-field\">\n <label class=\"field-label required\" for=\"name\">Role Name</label>\n <input\n id=\"name\"\n type=\"text\"\n class=\"field-input\"\n formControlName=\"name\"\n placeholder=\"Sales Manager\"\n [class.error]=\"roleForm.get('name')?.invalid && roleForm.get('name')?.touched\"\n />\n @if (roleForm.get('name')?.invalid && roleForm.get('name')?.touched) {\n <div class=\"field-error\">\n <i class=\"fa-solid fa-exclamation-circle\"></i>\n @if (roleForm.get('name')?.errors?.['required']) {\n Role name is required\n }\n @if (roleForm.get('name')?.errors?.['maxlength']) {\n Role name cannot exceed 50 characters\n }\n </div>\n }\n </div>\n <div class=\"form-field\">\n <label class=\"field-label\" for=\"description\">Description</label>\n <textarea\n id=\"description\"\n class=\"field-textarea\"\n formControlName=\"description\"\n placeholder=\"Describe the role's purpose and responsibilities...\"\n rows=\"4\"\n ></textarea>\n <div class=\"field-hint\">\n Provide a clear description of what this role is for and what permissions it should have.\n </div>\n </div>\n @if (isEditMode) {\n <div class=\"form-field\">\n <label class=\"field-label\" for=\"directoryId\">Directory ID</label>\n <input\n id=\"directoryId\"\n type=\"text\"\n class=\"field-input\"\n formControlName=\"directoryId\"\n placeholder=\"External directory identifier\"\n />\n <div class=\"field-hint\">\n External directory identifier for syncing with Active Directory or other systems.\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n <!-- Role Type Section -->\n <div class=\"content-section\">\n <div class=\"form-section\">\n <div class=\"section-header\">\n <h3 class=\"section-title\">\n <i class=\"fa-solid fa-cog\"></i>\n Role Type\n </h3>\n <p class=\"section-description\">System or custom role classification</p>\n </div>\n <div class=\"form-grid\">\n @if (isSystemRole) {\n <div class=\"alert alert-warning\">\n <i class=\"fa-solid fa-shield-halved\"></i>\n <div>\n <strong>System Role</strong>\n <p>This is a system-defined role. Some properties may be limited for editing to maintain system integrity.</p>\n </div>\n </div>\n } @else {\n <div class=\"alert alert-info\">\n <i class=\"fa-solid fa-user-tag\"></i>\n <div>\n <strong>Custom Role</strong>\n <p>This is a custom role that can be fully configured and modified.</p>\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n <!-- Permissions Preview Section -->\n @if (isEditMode) {\n <div class=\"content-section\">\n <div class=\"form-section\">\n <div class=\"section-header\">\n <h3 class=\"section-title\">\n <i class=\"fa-solid fa-key\"></i>\n Permissions\n </h3>\n <p class=\"section-description\">Manage role permissions and access rights</p>\n </div>\n <div class=\"form-grid\">\n <div class=\"alert alert-info\">\n <i class=\"fa-solid fa-info-circle\"></i>\n <div>\n <strong>Permission Management</strong>\n <p>Role permissions can be managed in the <strong>Permissions</strong> tab of the settings dashboard. Use that interface to configure specific access rights for this role.</p>\n </div>\n </div>\n </div>\n </div>\n </div>\n }\n <!-- Modal Footer -->\n <div class=\"modal-footer\">\n <button\n type=\"submit\"\n class=\"btn btn-primary\"\n [disabled]=\"roleForm.invalid || isLoading\"\n >\n @if (isLoading) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n Saving...\n } @else {\n <i class=\"fa-solid fa-save\"></i>\n {{ isEditMode ? 'Update Role' : 'Create Role' }}\n }\n </button>\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"onCancel()\">\n <i class=\"fa-solid fa-times\"></i>\n Cancel\n </button>\n </div>\n </form>\n </div>\n </div>\n </div>\n}\n"]}
|
|
@@ -33,7 +33,7 @@ export declare class RoleManagementComponent extends BaseDashboard implements On
|
|
|
33
33
|
expandedRoleId: string | null;
|
|
34
34
|
rolePermissions: Map<string, string[]>;
|
|
35
35
|
protected destroy$: Subject<void>;
|
|
36
|
-
private metadata;
|
|
36
|
+
private get metadata();
|
|
37
37
|
constructor(cdr: ChangeDetectorRef, ngZone: NgZone);
|
|
38
38
|
GetResourceDisplayName(data: ResourceData): Promise<string>;
|
|
39
39
|
protected initDashboard(): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"role-management.component.d.ts","sourceRoot":"","sources":["../../../src/lib/role-management/role-management.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,SAAS,EAAE,iBAAiB,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAChF,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,MAAM,CAAC;AAGhD,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAC3E,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAE1D,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,qCAAqC,CAAC;;AAEvF,UAAU,SAAS;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,UAAU,aAAa;IACrB,IAAI,EAAE,KAAK,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAClC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,qBAOa,uBAAwB,SAAQ,aAAc,YAAW,SAAS;
|
|
1
|
+
{"version":3,"file":"role-management.component.d.ts","sourceRoot":"","sources":["../../../src/lib/role-management/role-management.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,SAAS,EAAE,iBAAiB,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAChF,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,MAAM,CAAC;AAGhD,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAC3E,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAE1D,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,qCAAqC,CAAC;;AAEvF,UAAU,SAAS;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,UAAU,aAAa;IACrB,IAAI,EAAE,KAAK,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAClC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,qBAOa,uBAAwB,SAAQ,aAAc,YAAW,SAAS;IAsCjE,OAAO,CAAC,GAAG;IAAqB,OAAO,CAAC,MAAM;IApCnD,KAAK,EAAE,YAAY,EAAE,CAAM;IAC3B,aAAa,EAAE,YAAY,EAAE,CAAM;IACnC,YAAY,EAAE,YAAY,GAAG,IAAI,CAAQ;IACzC,SAAS,UAAS;IAClB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAQ;IAG5B,cAAc,UAAS;IACvB,cAAc,EAAE,cAAc,GAAG,IAAI,CAAQ;IAG7C,KAAK,EAAE,SAAS,CAKrB;IAGK,QAAQ,iCAGZ;IAGI,gBAAgB,UAAS;IACzB,cAAc,UAAS;IACvB,iBAAiB,UAAS;IAC1B,iBAAiB,UAAS;IAC1B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAQ;IAGrC,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAa;IAE1D,UAAmB,QAAQ,gBAAuB;IAClD,OAAO,KAAK,QAAQ,GAAiC;gBACjC,GAAG,EAAE,iBAAiB,EAAU,MAAM,EAAE,MAAM;IAI5D,sBAAsB,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC;IAIjE,SAAS,CAAC,aAAa,IAAI,IAAI;IAI/B,SAAS,CAAC,QAAQ,IAAI,IAAI;IAIjB,WAAW,IAAI,IAAI;IAMf,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;YAsB/B,SAAS;IAWvB,OAAO,CAAC,uBAAuB;IAY/B,OAAO,CAAC,YAAY;IAwBpB,OAAO,CAAC,cAAc;IAWf,YAAY,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO;IAOzC,cAAc,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAKlC,kBAAkB,CAAC,IAAI,EAAE,KAAK,GAAG,QAAQ,GAAG,QAAQ,GAAG,IAAI;IAI3D,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,IAAI;IAOnD,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAIzC,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAIvC,aAAa,IAAI,IAAI;IAOrB,QAAQ,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI;IAQlC,iBAAiB,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI;IAKrC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IA6BjC,WAAW,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM;IAOvC,gBAAgB,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM;IAI5C,gBAAgB,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM;IAI5C,WAAW,IAAI,IAAI;IAInB,kBAAkB,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI;yCA/O9C,uBAAuB;2CAAvB,uBAAuB;CAwPnC"}
|
|
@@ -7,7 +7,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
import { Component } from '@angular/core';
|
|
8
8
|
import { Subject, BehaviorSubject } from 'rxjs';
|
|
9
9
|
import { takeUntil, debounceTime, distinctUntilChanged } from 'rxjs/operators';
|
|
10
|
-
import { RunView
|
|
10
|
+
import { RunView } from '@memberjunction/core';
|
|
11
11
|
import { BaseDashboard } from '@memberjunction/ng-shared';
|
|
12
12
|
import { RegisterClass } from '@memberjunction/global';
|
|
13
13
|
import * as i0 from "@angular/core";
|
|
@@ -328,7 +328,7 @@ let RoleManagementComponent = class RoleManagementComponent extends BaseDashboar
|
|
|
328
328
|
// Role permissions (simplified view)
|
|
329
329
|
rolePermissions = new Map();
|
|
330
330
|
destroy$ = new Subject();
|
|
331
|
-
metadata
|
|
331
|
+
get metadata() { return this.ProviderToUse; }
|
|
332
332
|
constructor(cdr, ngZone) {
|
|
333
333
|
super();
|
|
334
334
|
this.cdr = cdr;
|
|
@@ -370,7 +370,7 @@ let RoleManagementComponent = class RoleManagementComponent extends BaseDashboar
|
|
|
370
370
|
}
|
|
371
371
|
}
|
|
372
372
|
async loadRoles() {
|
|
373
|
-
const rv =
|
|
373
|
+
const rv = RunView.FromMetadataProvider(this.ProviderToUse);
|
|
374
374
|
const result = await rv.RunView({
|
|
375
375
|
EntityName: 'MJ: Roles',
|
|
376
376
|
ResultType: 'entity_object',
|