@memberjunction/ng-explorer-settings 6.1.2 → 6.2.0-edge.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/application-management/application-dialog/application-dialog.component.d.ts +1 -0
- 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 +12 -6
- package/dist/lib/application-management/application-dialog/application-dialog.component.js.map +1 -1
- package/dist/lib/application-management/application-management.component.d.ts +2 -1
- package/dist/lib/application-management/application-management.component.d.ts.map +1 -1
- package/dist/lib/application-management/application-management.component.js +2 -2
- package/dist/lib/application-management/application-management.component.js.map +1 -1
- package/dist/lib/user-management/transaction-group-refusals.d.ts +43 -0
- package/dist/lib/user-management/transaction-group-refusals.d.ts.map +1 -0
- package/dist/lib/user-management/transaction-group-refusals.js +45 -0
- package/dist/lib/user-management/transaction-group-refusals.js.map +1 -0
- 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 +17 -2
- package/dist/lib/user-management/user-dialog/user-dialog.component.js.map +1 -1
- package/dist/lib/user-management/user-management.component.d.ts.map +1 -1
- package/dist/lib/user-management/user-management.component.js +24 -9
- package/dist/lib/user-management/user-management.component.js.map +1 -1
- package/package.json +20 -20
|
@@ -55,7 +55,7 @@ function ApplicationManagementComponent_Conditional_16_For_2_Conditional_20_Cond
|
|
|
55
55
|
} if (rf & 2) {
|
|
56
56
|
const appEntity_r6 = i0.ɵɵnextContext().$implicit;
|
|
57
57
|
i0.ɵɵadvance(2);
|
|
58
|
-
i0.ɵɵtextInterpolate(ctx.
|
|
58
|
+
i0.ɵɵtextInterpolate(ctx.DisplayNameOrName);
|
|
59
59
|
i0.ɵɵadvance(2);
|
|
60
60
|
i0.ɵɵconditional(appEntity_r6.DefaultForNewUser ? 4 : -1);
|
|
61
61
|
i0.ɵɵadvance(2);
|
|
@@ -667,7 +667,7 @@ ApplicationManagementComponent = __decorate([
|
|
|
667
667
|
export { ApplicationManagementComponent };
|
|
668
668
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ApplicationManagementComponent, [{
|
|
669
669
|
type: Component,
|
|
670
|
-
args: [{ standalone: false, selector: 'mj-application-management', template: "<!--\n Interior chrome \u2014 see Section 10 of explorer-chrome-conventions.\n-->\n<mj-page-header-interior\n AriaLabel=\"Filter applications\"\n Title=\"Applications\"\n Subtitle=\"Configure applications and their entity associations\">\n <!-- X-of-Y filtered count earns its meta spot per chrome conventions \u00A72.\n Partition badges (entities/public) stay dropped \u2014 Active/Inactive\n chips in [toolbar] handle the partition signal. -->\n <div meta>\n <mj-stat-badge\n [Count]=\"filteredApplications.length\"\n [Total]=\"applications.length\"\n Label=\"applications\">\n </mj-stat-badge>\n </div>\n <div toolbar>\n <!-- Concise-chrome control bar: search \u00B7 Filter. Status lives behind the\n one Filter button; applied filters show as removable chips below the\n card. On mobile the popover docks as a bottom sheet. -->\n <mj-page-search\n Placeholder=\"Search applications by name or description...\"\n [Value]=\"filters$.value.search\"\n (ValueChange)=\"updateFilter({ search: $event })\">\n </mj-page-search>\n <mj-filter-popover\n Label=\"Filters\"\n Icon=\"fa-solid fa-filter\"\n [ActiveCount]=\"TotalActiveFilterCount\"\n [ShowClearAll]=\"TotalActiveFilterCount > 0\"\n (ClearAllRequested)=\"clearAllAppliedFilters()\">\n <mj-filter-panel\n [Fields]=\"filterFields\"\n [Values]=\"filterValues\"\n (ValuesChange)=\"onFilterPanelChange($event)\"\n (Reset)=\"clearAllAppliedFilters()\">\n </mj-filter-panel>\n </mj-filter-popover>\n </div>\n <div actions>\n <mj-refresh-button [Loading]=\"isLoading\" (Clicked)=\"refreshData()\"></mj-refresh-button>\n <button mjButton variant=\"primary\" size=\"sm\" (click)=\"createNewApplication()\" aria-label=\"Add new application\">\n <i class=\"fa-solid fa-plus\"></i> <span class=\"mj-action-label\">Add Application</span>\n </button>\n </div>\n</mj-page-header-interior>\n\n<!-- Scrollable Content Section \u2014 owned by <mj-page-body-interior>. -->\n<mj-page-body-interior>\n <!-- Loading State -->\n @if (isLoading) {\n <div class=\"loading-container\" role=\"status\" aria-live=\"polite\">\n <mj-loading text=\"Loading applications...\"></mj-loading>\n </div>\n }\n\n <!-- Error State -->\n @if (error && !isLoading) {\n <div class=\"error-container\" role=\"alert\">\n <div class=\"error-content\">\n <i class=\"fa-solid fa-exclamation-triangle error-icon\" aria-hidden=\"true\"></i>\n <p class=\"error-message\">{{ error }}</p>\n <button mjButton variant=\"primary\" (click)=\"loadInitialData()\">\n <i class=\"fa-solid fa-refresh\"></i>\n Try Again\n </button>\n </div>\n </div>\n }\n\n <!-- Applications List -->\n @if (!isLoading && !error) {\n <div class=\"applications-list\" role=\"list\">\n @for (app of filteredApplications; track app.ID) {\n <div class=\"app-card\" [class.expanded]=\"isAppExpanded(app.ID)\" role=\"listitem\">\n <div class=\"app-header\" (click)=\"toggleAppExpansion(app.ID)\" role=\"button\" [attr.aria-expanded]=\"isAppExpanded(app.ID)\">\n <div class=\"app-info\">\n <div class=\"app-icon-wrapper\">\n <i [class]=\"'fa-solid ' + getAppIcon(app)\" aria-hidden=\"true\"></i>\n </div>\n <div class=\"app-details\">\n <h3 class=\"app-name\">{{ app.Name }}</h3>\n <p class=\"app-description\">{{ app.Description || 'No description available' }}</p>\n </div>\n </div>\n\n <div class=\"app-meta\">\n <span class=\"status-badge\" [class]=\"getAppStatusClass(app)\">\n {{ getAppStatusLabel(app) }}\n </span>\n <div class=\"app-actions\" (click)=\"$event.stopPropagation()\">\n <button\n mjButton variant=\"flat\" size=\"sm\"\n (click)=\"editApplication(app)\"\n title=\"Edit\"\n [attr.aria-label]=\"'Edit ' + app.Name\"\n >\n <i class=\"fa-solid fa-edit\"></i>\n </button>\n <button\n mjButton variant=\"danger\" size=\"sm\"\n (click)=\"confirmDeleteApplication(app)\"\n title=\"Delete\"\n [attr.aria-label]=\"'Delete ' + app.Name\"\n >\n <i class=\"fa-solid fa-trash\"></i>\n </button>\n </div>\n <button class=\"expand-btn\" aria-label=\"Toggle details\">\n <i class=\"fa-solid fa-chevron-down\" aria-hidden=\"true\"></i>\n </button>\n </div>\n </div>\n\n @if (isAppExpanded(app.ID)) {\n <div class=\"app-content\">\n <!-- Mobile-only actions bar -->\n <div class=\"mobile-actions-bar\" (click)=\"$event.stopPropagation()\">\n <span class=\"status-badge\" [class]=\"getAppStatusClass(app)\">\n {{ getAppStatusLabel(app) }}\n </span>\n <div class=\"mobile-action-buttons\">\n <button\n mjButton variant=\"flat\" size=\"sm\"\n (click)=\"editApplication(app)\"\n title=\"Edit\"\n [attr.aria-label]=\"'Edit ' + app.Name\"\n >\n <i class=\"fa-solid fa-edit\"></i>\n <span class=\"btn-label\">Edit</span>\n </button>\n <button\n mjButton variant=\"danger\" size=\"sm\"\n (click)=\"confirmDeleteApplication(app)\"\n title=\"Delete\"\n [attr.aria-label]=\"'Delete ' + app.Name\"\n >\n <i class=\"fa-solid fa-trash\"></i>\n <span class=\"btn-label\">Delete</span>\n </button>\n </div>\n </div>\n\n <div class=\"app-stats\">\n <div class=\"stat-item\">\n <i class=\"fa-solid fa-database\" aria-hidden=\"true\"></i>\n <span class=\"stat-label\">Entities:</span>\n <span class=\"stat-value\">{{ getAppEntities(app.ID).length }}</span>\n </div>\n <div class=\"stat-item\">\n <i class=\"fa-solid fa-calendar\" aria-hidden=\"true\"></i>\n <span class=\"stat-label\">Created:</span>\n <span class=\"stat-value\">{{ app.__mj_CreatedAt | date:'short' }}</span>\n </div>\n <div class=\"stat-item\">\n <i class=\"fa-solid fa-clock\" aria-hidden=\"true\"></i>\n <span class=\"stat-label\">Updated:</span>\n <span class=\"stat-value\">{{ app.__mj_UpdatedAt | date:'short' }}</span>\n </div>\n </div>\n\n <div class=\"entities-section\">\n <h4 class=\"section-title\">\n <i class=\"fa-solid fa-database\" aria-hidden=\"true\"></i>\n Application Entities\n </h4>\n\n @if (getAppEntities(app.ID).length > 0) {\n <div class=\"entities-grid\">\n @for (appEntity of getAppEntities(app.ID); track appEntity.ID) {\n @if (getEntityInfo(appEntity.EntityID); as entity) {\n <div class=\"entity-item\">\n <div class=\"entity-name\">{{ entity.Name }}</div>\n <div class=\"entity-meta\">\n @if (appEntity.DefaultForNewUser) {\n <span class=\"entity-badge public\">Public</span>\n }\n <span class=\"entity-sequence\">Order: {{ appEntity.Sequence }}</span>\n </div>\n </div>\n }\n }\n </div>\n } @else {\n <p class=\"no-entities\">No entities configured for this application</p>\n }\n </div>\n </div>\n }\n </div>\n }\n\n @if (filteredApplications.length === 0) {\n <mj-empty-state\n [Variant]=\"IsListNarrowed ? 'no-results' : 'empty'\"\n Icon=\"fa-solid fa-cubes\"\n Title=\"No applications found\"\n Message=\"Try adjusting your filters or create a new application\"\n [ActionText]=\"IsListNarrowed ? 'Reset filters' : ''\"\n ActionIcon=\"fa-solid fa-rotate-left\"\n (Action)=\"resetAllFiltersAndSearch()\" />\n }\n </div>\n }\n </mj-page-body-interior>\n\n <!-- Application Dialog -->\n <mj-application-dialog\n [visible]=\"showApplicationDialog\"\n [data]=\"applicationDialogData\"\n (result)=\"onApplicationDialogResult($event)\"\n ></mj-application-dialog>\n\n <!-- Delete Confirmation Dialog -->\n @if (showDeleteConfirm && selectedApp) {\n <div class=\"modal-backdrop\" (click)=\"showDeleteConfirm = false\" role=\"presentation\">\n <div\n class=\"modal-dialog\"\n (click)=\"$event.stopPropagation()\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"delete-dialog-title\"\n aria-describedby=\"delete-dialog-description\"\n >\n <div class=\"modal-header\">\n <h3 class=\"modal-title\" id=\"delete-dialog-title\">\n <i class=\"fa-solid fa-exclamation-triangle text-danger\" aria-hidden=\"true\"></i>\n Confirm Delete\n </h3>\n <button\n class=\"modal-close\"\n (click)=\"showDeleteConfirm = false\"\n aria-label=\"Close dialog\"\n >\n <i class=\"fa-solid fa-times\" aria-hidden=\"true\"></i>\n </button>\n </div>\n <div class=\"modal-body\" id=\"delete-dialog-description\">\n <p>Are you sure you want to delete the application <strong>{{ selectedApp.Name }}</strong>?</p>\n <mj-alert Variant=\"warning\" Message=\"This will remove all entity associations for this application.\" />\n </div>\n <div class=\"modal-footer\">\n <!-- Primary action (Delete) on LEFT per MD3 design guide -->\n <button mjButton variant=\"danger\" (click)=\"deleteApplication()\" [disabled]=\"isLoading\">\n @if (isLoading) {\n <i class=\"fa-solid fa-spinner fa-spin\" aria-hidden=\"true\"></i>\n Deleting...\n } @else {\n <i class=\"fa-solid fa-trash\" aria-hidden=\"true\"></i>\n Delete Application\n }\n </button>\n <button mjButton (click)=\"showDeleteConfirm = false\">\n Cancel\n </button>\n </div>\n </div>\n </div>\n }\n", styles: ["/* =============================================================================\n Shared Admin Patterns - MJ Design System\n Common styles shared across all admin settings components.\n Component-specific CSS files override these values where needed.\n ============================================================================= */\n\n/* -----------------------------------------------------------------------------\n Host & Container\n ----------------------------------------------------------------------------- */\n:host {\n display: flex;\n flex-direction: column;\n height: 100%;\n width: 100%;\n overflow: hidden;\n}\n\n/* -----------------------------------------------------------------------------\n Sticky Header\n ----------------------------------------------------------------------------- */\n.sticky-header {\n flex-shrink: 0;\n background: var(--mj-bg-page);\n border-bottom: 1px solid var(--mj-border-default);\n box-shadow: var(--mj-shadow-sm);\n z-index: 10;\n}\n\n/* -----------------------------------------------------------------------------\n Scrollable Content\n ----------------------------------------------------------------------------- */\n.scrollable-content {\n flex: 1;\n overflow-y: auto;\n overflow-x: hidden;\n -webkit-overflow-scrolling: touch;\n padding: 1rem;\n background: var(--mj-bg-surface-sunken);\n}\n\n@media (min-width: 768px) {\n .scrollable-content {\n padding: 1.5rem 2rem;\n }\n}\n\n@media (min-width: 1024px) {\n .scrollable-content {\n padding: 2rem;\n }\n}\n\n@media (min-width: 1440px) {\n .scrollable-content {\n padding: 2rem 2.5rem;\n }\n}\n\n/* -----------------------------------------------------------------------------\n Action Buttons (In Sticky Header)\n ----------------------------------------------------------------------------- */\n.action-buttons {\n flex-shrink: 0;\n display: flex;\n gap: 0.75rem;\n justify-content: flex-end;\n padding: 0.75rem 1rem;\n background: var(--mj-bg-page);\n}\n\n@media (min-width: 768px) {\n .action-buttons {\n padding: 1rem 1.5rem;\n }\n}\n\n@media (max-width: 639px) {\n .action-buttons {\n justify-content: center;\n flex-wrap: wrap;\n }\n}\n\n/* -----------------------------------------------------------------------------\n Statistics Cards Grid - Static Display (Non-interactive)\n ----------------------------------------------------------------------------- */\n.mj-grid-4 {\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n gap: 0.5rem;\n padding: 0 1rem 0.75rem 1rem;\n background: var(--mj-bg-page);\n}\n\n@media (min-width: 768px) {\n .mj-grid-4 {\n grid-template-columns: repeat(4, 1fr);\n padding: 0 1.5rem 1rem 1.5rem;\n gap: 0.75rem;\n }\n}\n\n@media (min-width: 1024px) {\n .mj-grid-4 {\n gap: 1rem;\n }\n}\n\n/* Static Card - No hover effects */\n.mj-card {\n background: var(--mj-bg-surface-card);\n border-radius: var(--mj-radius-lg);\n padding: 1rem;\n box-shadow: var(--mj-shadow-sm);\n display: flex;\n align-items: center;\n gap: 1rem;\n border: 1px solid var(--mj-border-default);\n cursor: default;\n pointer-events: none;\n}\n\n@media (min-width: 768px) {\n .mj-card {\n padding: 1rem 1.25rem;\n }\n}\n\n/* -----------------------------------------------------------------------------\n Stat Icon Containers\n ----------------------------------------------------------------------------- */\n.stat-icon {\n width: 48px;\n height: 48px;\n border-radius: var(--mj-radius-lg);\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 1.25rem;\n flex-shrink: 0;\n}\n\n@media (min-width: 768px) {\n .stat-icon {\n width: 52px;\n height: 52px;\n font-size: 1.375rem;\n }\n}\n\n.stat-icon-total {\n background: var(--mj-color-accent-300);\n color: var(--mj-brand-primary);\n}\n\n.stat-icon-active {\n background: var(--mj-color-success-100);\n color: var(--mj-status-success);\n}\n\n/* -----------------------------------------------------------------------------\n Stat Content Typography\n ----------------------------------------------------------------------------- */\n.stat-content {\n flex: 1;\n min-width: 0;\n}\n\n.stat-content .stat-value {\n font-size: 1.75rem;\n font-weight: 700;\n color: var(--mj-text-primary);\n line-height: 1;\n letter-spacing: -0.02em;\n}\n\n@media (min-width: 768px) {\n .stat-content .stat-value {\n font-size: 2rem;\n }\n}\n\n.stat-content .stat-label {\n font-size: 0.75rem;\n font-weight: 500;\n color: var(--mj-text-secondary);\n text-transform: uppercase;\n letter-spacing: 0.05em;\n margin-top: 0.25rem;\n}\n\n@media (min-width: 768px) {\n .stat-content .stat-label {\n font-size: 0.8125rem;\n }\n}\n\n/* -----------------------------------------------------------------------------\n Filters Section\n ----------------------------------------------------------------------------- */\n.filters-section {\n flex-shrink: 0;\n background: var(--mj-bg-surface-card);\n margin: 0 1rem 0.75rem 1rem;\n padding: 0.75rem;\n border-radius: var(--mj-radius-lg);\n border: 1px solid var(--mj-border-default);\n}\n\n@media (min-width: 768px) {\n .filters-section {\n margin: 0 1.5rem 1rem 1.5rem;\n padding: 1rem 1.25rem;\n }\n}\n\n.filters-row {\n display: flex;\n gap: 1rem;\n align-items: flex-end;\n flex-wrap: wrap;\n}\n\n@media (min-width: 768px) {\n .filters-row {\n gap: 1.5rem;\n }\n}\n\n.mj-filter-group {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n\n.mj-filter-label {\n font-size: 0.75rem;\n font-weight: 500;\n color: var(--mj-text-primary);\n text-transform: uppercase;\n letter-spacing: 0.05em;\n}\n\n.mj-filter-buttons {\n display: flex;\n background: var(--mj-bg-surface-sunken);\n border-radius: var(--mj-radius-full);\n padding: 4px;\n gap: 2px;\n}\n\n/* -----------------------------------------------------------------------------\n Search Input\n ----------------------------------------------------------------------------- */\n.mj-search {\n position: relative;\n flex: 1;\n min-width: 200px;\n}\n\n@media (min-width: 640px) {\n .mj-search {\n min-width: 280px;\n }\n}\n\n@media (min-width: 1024px) {\n .mj-search {\n min-width: 400px;\n max-width: 600px;\n }\n}\n\n@media (min-width: 1440px) {\n .mj-search {\n min-width: 500px;\n max-width: 800px;\n }\n}\n\n.mj-search .mj-search-icon {\n position: absolute;\n left: 1rem;\n top: 50%;\n transform: translateY(-50%);\n color: var(--mj-text-secondary);\n font-size: 1rem;\n pointer-events: none;\n transition: color 0.2s ease;\n}\n\n@media (min-width: 1024px) {\n .mj-search .mj-search-icon {\n left: 1.25rem;\n font-size: 1.125rem;\n }\n}\n\n.mj-search .mj-search-input {\n width: 100%;\n padding: 0.875rem 1rem 0.875rem 2.75rem;\n border: 2px solid var(--mj-border-default);\n border-radius: var(--mj-radius-full);\n font-size: 1rem;\n background: var(--mj-bg-page);\n color: var(--mj-text-primary);\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n min-height: 44px;\n}\n\n@media (min-width: 1024px) {\n .mj-search .mj-search-input {\n padding: 1rem 1.25rem 1rem 3.25rem;\n font-size: 1.0625rem;\n }\n}\n\n@media (min-width: 1440px) {\n .mj-search .mj-search-input {\n padding: 1.125rem 1.5rem 1.125rem 3.5rem;\n }\n}\n\n.mj-search .mj-search-input::placeholder {\n color: var(--mj-text-secondary);\n}\n\n.mj-search .mj-search-input:hover {\n border-color: var(--mj-brand-primary);\n background: var(--mj-bg-surface);\n}\n\n.mj-search .mj-search-input:focus {\n outline: none;\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 3px rgba(0, 118, 182, 0.2);\n background: var(--mj-bg-page);\n}\n\n.mj-search:focus-within .mj-search-icon {\n color: var(--mj-brand-primary);\n}\n\n/* -----------------------------------------------------------------------------\n Content Area\n ----------------------------------------------------------------------------- */\n.content-area {\n flex: 1 1 auto;\n overflow: visible;\n position: relative;\n background: var(--mj-bg-page);\n border-radius: var(--mj-radius-xl);\n box-shadow: var(--mj-shadow-sm);\n padding: 1rem;\n border: 1px solid var(--mj-border-default);\n}\n\n@media (min-width: 768px) {\n .content-area {\n padding: 1.5rem;\n }\n}\n\n/* -----------------------------------------------------------------------------\n Expand Button\n ----------------------------------------------------------------------------- */\n.expand-btn {\n padding: 0.625rem;\n border: none;\n background: transparent;\n color: var(--mj-text-secondary);\n font-size: 1rem;\n cursor: pointer;\n transition: all 0.2s ease;\n border-radius: var(--mj-radius-full);\n min-width: 44px;\n min-height: 44px;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n}\n\n.expand-btn:hover {\n background: var(--mj-brand-primary);\n color: var(--mj-brand-on-primary);\n}\n\n.expand-btn i {\n transition: transform 0.3s cubic-bezier(0, 0, 0.2, 1);\n}\n\n/* -----------------------------------------------------------------------------\n Status Badge\n ----------------------------------------------------------------------------- */\n.status-badge {\n display: inline-flex;\n align-items: center;\n gap: 0.375rem;\n padding: 0.375rem 0.875rem;\n border-radius: var(--mj-radius-full);\n font-size: 0.75rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.03em;\n}\n\n.status-badge.status-active {\n background: var(--mj-color-success-100);\n color: var(--mj-color-success-800);\n border: 1px solid var(--mj-status-success);\n}\n\n.status-badge.status-inactive {\n background: var(--mj-color-error-100);\n color: var(--mj-status-error);\n border: 1px solid var(--mj-status-error);\n}\n\n.status-badge i {\n font-size: 0.625rem;\n}\n\n/* -----------------------------------------------------------------------------\n Mobile Actions Bar\n ----------------------------------------------------------------------------- */\n.mobile-actions-bar {\n display: none;\n}\n\n.mobile-action-buttons {\n display: flex;\n gap: 0.5rem;\n}\n\n/* -----------------------------------------------------------------------------\n Empty State\n ----------------------------------------------------------------------------- */\n.empty-state {\n text-align: center;\n padding: 4rem 2rem;\n}\n\n.empty-state .empty-icon {\n font-size: 4rem;\n color: var(--mj-border-default);\n margin-bottom: 1.5rem;\n}\n\n.empty-state .empty-text {\n font-size: 1.25rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin: 0 0 0.5rem 0;\n}\n\n.empty-state .empty-subtext {\n font-size: 1rem;\n color: var(--mj-text-secondary);\n margin: 0;\n}\n\n/* -----------------------------------------------------------------------------\n Loading State\n ----------------------------------------------------------------------------- */\n.loading-container {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 4rem 2rem;\n}\n\n/* -----------------------------------------------------------------------------\n Error State\n ----------------------------------------------------------------------------- */\n.error-container {\n text-align: center;\n padding: 4rem 2rem;\n}\n\n.error-container .error-content {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 1rem;\n}\n\n.error-container .error-icon {\n font-size: 3.5rem;\n color: var(--mj-status-error);\n margin-bottom: 1rem;\n}\n\n.error-container .error-message {\n font-size: 1.0625rem;\n color: var(--mj-text-primary);\n margin: 0 0 1.5rem 0;\n}\n\n/* -----------------------------------------------------------------------------\n Modal Dialog\n ----------------------------------------------------------------------------- */\n.modal-backdrop {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.4);\n backdrop-filter: blur(4px);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1000;\n padding: 1rem;\n animation: fadeIn 0.2s cubic-bezier(0, 0, 0.2, 1);\n}\n\n.modal-dialog {\n background: var(--mj-bg-page);\n border-radius: var(--mj-radius-2xl);\n box-shadow: var(--mj-shadow-2xl);\n max-width: 500px;\n width: 100%;\n max-height: 90vh;\n overflow: hidden;\n animation: slideUp 0.3s cubic-bezier(0, 0, 0.2, 1);\n}\n\n.modal-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 1.5rem;\n border-bottom: 1px solid var(--mj-border-default);\n}\n\n.modal-header .modal-title {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n font-size: 1.375rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin: 0;\n}\n\n.modal-header .modal-close {\n padding: 0.625rem;\n border: none;\n background: transparent;\n color: var(--mj-text-secondary);\n font-size: 1.25rem;\n cursor: pointer;\n border-radius: var(--mj-radius-full);\n transition: all 0.2s ease;\n min-width: 44px;\n min-height: 44px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.modal-header .modal-close:hover {\n background: var(--mj-brand-primary);\n color: var(--mj-brand-on-primary);\n}\n\n.modal-body {\n padding: 1.5rem;\n}\n\n.modal-body p {\n font-size: 1rem;\n color: var(--mj-text-primary);\n margin: 0 0 1rem 0;\n line-height: 1.5;\n}\n\n.modal-body p:last-child {\n margin-bottom: 0;\n}\n\n.modal-footer {\n display: flex;\n justify-content: flex-start;\n gap: 0.75rem;\n padding: 1.5rem;\n border-top: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-sunken);\n}\n\n@media (max-width: 639px) {\n .modal-dialog {\n width: 95%;\n max-height: 85vh;\n }\n\n .modal-header {\n padding: 1rem;\n }\n\n .modal-header .modal-title {\n font-size: 1.125rem;\n }\n\n .modal-body {\n padding: 1rem;\n }\n\n .modal-footer {\n padding: 1rem;\n flex-direction: column;\n }\n\n}\n\n/* The legacy \"MJ Button System\" rule block (.mj-btn / .mj-btn-primary /\n .mj-btn-secondary / .mj-btn-ghost / .mj-btn-danger / .mj-btn-sm /\n .mj-btn-icon-only / .mj-btn-icon-mobile) was removed 2026-05-19. These\n rules pre-dated the mjButton directive standardization (the directive\n uses BEM modifiers like .mj-btn--primary, single-dash names like\n .mj-btn-primary don't match it anyway). The base .mj-btn rule WAS active\n though \u2014 its component-scoped specificity outranked button.scss's global\n directive styles, so admin sub-page buttons rendered pill-shaped + 44px\n regardless of the variant/size requested via mjButton inputs. Removing the\n rule lets every sub-page fall back to the directive's intended look. */\n\n/* -----------------------------------------------------------------------------\n Filter Button (Mobile Only)\n ----------------------------------------------------------------------------- */\n.filter-button {\n display: none;\n align-items: center;\n justify-content: center;\n gap: 0.5rem;\n padding: 0.75rem 1.25rem;\n border: 2px solid var(--mj-border-default);\n background: var(--mj-bg-page);\n color: var(--mj-text-primary);\n border-radius: var(--mj-radius-full);\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 min-height: 44px;\n min-width: 44px;\n}\n\n.filter-button:hover {\n border-color: var(--mj-brand-primary);\n background: var(--mj-bg-surface);\n}\n\n.filter-button:active {\n background: var(--mj-brand-primary);\n color: var(--mj-brand-on-primary);\n border-color: var(--mj-brand-primary);\n}\n\n.filter-button i {\n font-size: 1rem;\n}\n\n/* -----------------------------------------------------------------------------\n Filter Modal (Mobile Bottom Sheet)\n ----------------------------------------------------------------------------- */\n.filter-modal-backdrop {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.4);\n backdrop-filter: blur(4px);\n display: flex;\n align-items: flex-end;\n justify-content: center;\n z-index: 1000;\n animation: fadeIn 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n padding: 0;\n}\n\n.filter-modal {\n background: var(--mj-bg-page);\n border-radius: var(--mj-radius-2xl) var(--mj-radius-2xl) 0 0;\n box-shadow: var(--mj-shadow-2xl);\n width: 100%;\n max-height: 80vh;\n overflow: hidden;\n animation: slideUpFromBottom 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n display: flex;\n flex-direction: column;\n}\n\n.filter-modal-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 1.25rem 1.5rem;\n border-bottom: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n}\n\n.filter-modal-title {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n font-size: 1.25rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin: 0;\n}\n\n.filter-modal-title i {\n color: var(--mj-brand-primary);\n font-size: 1.375rem;\n}\n\n.filter-modal-close {\n padding: 0.625rem;\n border: none;\n background: transparent;\n color: var(--mj-text-secondary);\n font-size: 1.25rem;\n cursor: pointer;\n border-radius: var(--mj-radius-full);\n transition: all 0.2s ease;\n min-width: 44px;\n min-height: 44px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.filter-modal-close:hover {\n background: var(--mj-brand-primary);\n color: var(--mj-brand-on-primary);\n}\n\n.filter-modal-body {\n padding: 1.5rem;\n overflow-y: auto;\n -webkit-overflow-scrolling: touch;\n flex: 1;\n}\n\n.filter-modal-footer {\n display: flex;\n gap: 0.75rem;\n padding: 1.25rem 1.5rem;\n border-top: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-sunken);\n flex-shrink: 0;\n}\n\n.filter-modal-footer button {\n flex: 1;\n margin: 0;\n}\n\n/* Filter Options Container (Inside Modal) */\n.filter-options-container {\n display: flex;\n flex-direction: column;\n gap: 1.5rem;\n}\n\n.filter-group {\n display: flex;\n flex-direction: column;\n gap: 0.75rem;\n}\n\n.filter-group-label {\n font-size: 0.875rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n text-transform: uppercase;\n letter-spacing: 0.05em;\n margin: 0;\n}\n\n.filter-group-options {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n\n.filter-option {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n padding: 0.875rem 1rem;\n background: var(--mj-bg-surface-sunken);\n border: 2px solid var(--mj-border-default);\n border-radius: var(--mj-radius-lg);\n cursor: pointer;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n min-height: 56px;\n}\n\n.filter-option:hover {\n border-color: var(--mj-brand-primary);\n background: var(--mj-bg-surface-sunken);\n}\n\n.filter-option.selected {\n background: var(--mj-color-accent-300);\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-primary);\n}\n\n.filter-option input[type=\"checkbox\"],\n.filter-option input[type=\"radio\"] {\n width: 20px;\n height: 20px;\n cursor: pointer;\n accent-color: var(--mj-brand-primary);\n flex-shrink: 0;\n}\n\n.filter-option-label {\n flex: 1;\n font-size: 1rem;\n font-weight: 500;\n color: var(--mj-text-primary);\n}\n\n.filter-option.selected .filter-option-label {\n color: var(--mj-brand-primary);\n font-weight: 600;\n}\n\n/* -----------------------------------------------------------------------------\n Utility Classes\n ----------------------------------------------------------------------------- */\n.text-danger {\n color: var(--mj-status-error);\n}\n\n.text-warning {\n color: var(--mj-color-warning-500);\n}\n\n.text-success {\n color: var(--mj-status-success);\n}\n\n/* -----------------------------------------------------------------------------\n Animations\n ----------------------------------------------------------------------------- */\n@keyframes fadeIn {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n\n@keyframes slideUp {\n from {\n transform: translateY(20px);\n opacity: 0;\n }\n to {\n transform: translateY(0);\n opacity: 1;\n }\n}\n\n@keyframes slideDown {\n from {\n opacity: 0;\n transform: translateY(-10px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n@keyframes slideUpFromBottom {\n from {\n transform: translateY(100%);\n opacity: 0;\n }\n to {\n transform: translateY(0);\n opacity: 1;\n }\n}\n\n/* -----------------------------------------------------------------------------\n Accessibility: Focus Indicators\n ----------------------------------------------------------------------------- */\n.mj-search-input:focus-visible,\nbutton: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 Application Management Component - Component-Specific Styles\n Shared patterns are in ../shared/styles/_admin-patterns.css\n ============================================================================= */\n\n/* :host already provides flex column / 100% height / overflow hidden via\n _admin-patterns.css. No bespoke container wrapper \u2014 see Section 10 of\n explorer-chrome-conventions. */\n\n/* Status chip group inside <mj-page-header-interior>'s [toolbar] slot. */\n.status-chips {\n display: inline-flex;\n align-items: center;\n gap: var(--mj-space-2);\n flex-shrink: 0;\n}\n\n/* -----------------------------------------------------------------------------\n Stat Icon Overrides (Component-specific colors)\n ----------------------------------------------------------------------------- */\n.stat-icon-entities {\n background: var(--mj-color-warning-50);\n color: var(--mj-color-warning-700);\n}\n\n.stat-icon-public {\n background: var(--mj-color-success-100);\n color: var(--mj-status-success);\n}\n\n/* -----------------------------------------------------------------------------\n Applications List\n ----------------------------------------------------------------------------- */\n.applications-list {\n display: flex;\n flex-direction: column;\n gap: 1rem;\n}\n\n/* -----------------------------------------------------------------------------\n Application Card - Interactive\n ----------------------------------------------------------------------------- */\n.app-card {\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-xl);\n overflow: hidden;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n cursor: pointer;\n}\n\n.app-card:hover {\n box-shadow: var(--mj-shadow-md);\n border-color: var(--mj-brand-primary);\n}\n\n.app-card.expanded {\n box-shadow: var(--mj-shadow-lg);\n border-color: var(--mj-brand-primary);\n}\n\n.app-card.expanded .expand-btn i {\n transform: rotate(180deg);\n}\n\n.app-header {\n padding: 1.25rem 1.5rem;\n display: flex;\n justify-content: space-between;\n align-items: center;\n cursor: pointer;\n background: var(--mj-bg-surface);\n transition: background-color 0.2s ease;\n}\n\n.app-header:hover {\n background: var(--mj-bg-surface-sunken);\n}\n\n.app-info {\n display: flex;\n align-items: center;\n gap: 1rem;\n flex: 1;\n min-width: 0;\n}\n\n.app-icon-wrapper {\n width: 48px;\n height: 48px;\n border-radius: var(--mj-radius-lg);\n background: var(--mj-color-accent-300);\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-brand-primary);\n font-size: 1.25rem;\n flex-shrink: 0;\n}\n\n.app-details {\n flex: 1;\n min-width: 0;\n}\n\n.app-details .app-name {\n font-size: 1.0625rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin: 0 0 0.25rem 0;\n word-break: break-word;\n overflow-wrap: break-word;\n}\n\n.app-details .app-description {\n font-size: 0.9375rem;\n color: var(--mj-text-secondary);\n margin: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n@media (max-width: 639px) {\n .app-details .app-description {\n white-space: normal;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n }\n}\n\n.app-meta {\n display: flex;\n align-items: center;\n gap: 1rem;\n flex-shrink: 0;\n}\n\n/* -----------------------------------------------------------------------------\n Application Actions\n ----------------------------------------------------------------------------- */\n.app-actions {\n display: flex;\n gap: 0.5rem;\n}\n\n/* -----------------------------------------------------------------------------\n Application Content (Expanded)\n ----------------------------------------------------------------------------- */\n.app-content {\n padding: 1.5rem;\n background: var(--mj-bg-page);\n border-top: 1px solid var(--mj-border-default);\n animation: slideDown 0.3s cubic-bezier(0, 0, 0.2, 1);\n}\n\n.app-stats {\n display: flex;\n gap: 2rem;\n margin-bottom: 1.5rem;\n flex-wrap: wrap;\n}\n\n.app-stats .stat-item {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n font-size: 0.9375rem;\n}\n\n.app-stats .stat-item i {\n color: var(--mj-text-secondary);\n}\n\n.app-stats .stat-item .stat-label {\n color: var(--mj-text-secondary);\n}\n\n.app-stats .stat-item .stat-value {\n color: var(--mj-text-primary);\n font-weight: 600;\n}\n\n/* -----------------------------------------------------------------------------\n Entities Section\n ----------------------------------------------------------------------------- */\n.entities-section {\n padding: 1rem;\n background: var(--mj-bg-surface-sunken);\n border-radius: var(--mj-radius-md);\n}\n\n.entities-section .section-title {\n display: flex;\n align-items: center;\n gap: 0.625rem;\n font-size: 1rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin: 0 0 1rem 0;\n}\n\n.entities-section .section-title i {\n color: var(--mj-brand-primary);\n font-size: 1.125rem;\n}\n\n.entities-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));\n gap: 0.75rem;\n}\n\n@media (min-width: 640px) {\n .entities-grid {\n grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));\n }\n}\n\n.entity-item {\n padding: 0.75rem;\n background: var(--mj-bg-page);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md);\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.entity-item:hover {\n border-color: var(--mj-brand-primary);\n box-shadow: var(--mj-shadow-sm);\n}\n\n.entity-item .entity-name {\n font-size: 0.9375rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin-bottom: 0.25rem;\n}\n\n.entity-item .entity-meta {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n font-size: 0.8125rem;\n color: var(--mj-text-secondary);\n}\n\n.entity-badge {\n padding: 0.125rem 0.5rem;\n border-radius: var(--mj-radius-full);\n font-size: 0.6875rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.03em;\n}\n\n.entity-badge.public {\n background: var(--mj-color-success-100);\n color: var(--mj-color-success-800);\n border: 1px solid var(--mj-status-success);\n}\n\n.entity-sequence {\n font-size: 0.8125rem;\n color: var(--mj-text-secondary);\n}\n\n.no-entities {\n color: var(--mj-text-secondary);\n font-size: 0.9375rem;\n text-align: center;\n padding: 1rem;\n margin: 0;\n}\n\n/* -----------------------------------------------------------------------------\n Modal Body Override - warning callout\n ----------------------------------------------------------------------------- */\n.modal-body p {\n font-size: 1.0625rem;\n}\n\n\n/* -----------------------------------------------------------------------------\n Desktop (1440px+) - Large Screen Overrides\n ----------------------------------------------------------------------------- */\n@media (min-width: 1440px) {\n .application-management-container {\n margin: 0 auto;\n }\n\n .mj-grid-4 {\n padding: 0 2rem 1rem 2rem;\n gap: 1.25rem;\n }\n}\n\n/* -----------------------------------------------------------------------------\n Mobile-specific adjustments (< 640px)\n ----------------------------------------------------------------------------- */\n@media (max-width: 639px) {\n /* Hide stats cards on mobile to save space */\n .mj-grid-4 {\n display: none !important;\n }\n\n /* More compact scrollable content */\n .scrollable-content {\n padding: 0.5rem;\n }\n\n /* Modal styles */\n .modal-backdrop {\n padding: 0.5rem;\n }\n\n .modal-dialog {\n border-radius: var(--mj-radius-2xl);\n max-height: 95vh;\n }\n\n /* App Card Mobile Layout */\n .applications-list {\n gap: 0.5rem;\n }\n\n .app-card {\n border-radius: var(--mj-radius-md);\n overflow: hidden;\n }\n\n .app-header {\n flex-direction: row;\n align-items: center;\n justify-content: space-between;\n gap: 0.5rem;\n padding: 0.75rem;\n }\n\n .app-info {\n flex: 1;\n min-width: 0;\n flex-direction: row;\n align-items: flex-start;\n gap: 0.75rem;\n }\n\n .app-icon-wrapper {\n width: 36px;\n height: 36px;\n font-size: 0.875rem;\n flex-shrink: 0;\n border-radius: var(--mj-radius-md);\n }\n\n .app-details {\n flex: 1;\n min-width: 0;\n }\n\n .app-details .app-name {\n font-size: 0.9375rem;\n line-height: 1.3;\n margin-bottom: 0.125rem;\n }\n\n .app-details .app-description {\n font-size: 0.8125rem;\n -webkit-line-clamp: 1;\n line-clamp: 1;\n }\n\n /* On mobile: hide status and actions in header, show only expand button */\n .app-meta {\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: flex-end;\n padding-top: 0;\n border-top: none;\n width: auto;\n }\n\n .app-meta .status-badge {\n display: none;\n }\n\n .app-meta .app-actions {\n display: none;\n }\n\n .expand-btn {\n flex-shrink: 0;\n min-width: 36px;\n min-height: 36px;\n padding: 0.5rem;\n border-radius: var(--mj-radius-full);\n }\n\n .expand-btn i {\n font-size: 1rem;\n }\n\n /* Mobile actions bar - visible on mobile only */\n .mobile-actions-bar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 0.75rem;\n padding: 0.75rem;\n background: var(--mj-bg-surface-sunken);\n border-radius: var(--mj-radius-md);\n margin-bottom: 0.75rem;\n }\n\n .mobile-actions-bar .status-badge {\n padding: 0.25rem 0.625rem;\n font-size: 0.6875rem;\n }\n\n .mobile-action-buttons {\n display: flex;\n gap: 0.5rem;\n }\n\n .mobile-action-buttons .mj-btn-sm {\n display: flex;\n align-items: center;\n gap: 0.375rem;\n padding: 0.5rem 0.875rem;\n min-height: 36px;\n border-radius: var(--mj-radius-full);\n }\n\n .mobile-action-buttons .mj-btn-sm i {\n font-size: 0.875rem;\n }\n\n .mobile-action-buttons .btn-label {\n font-size: 0.8125rem;\n }\n\n .app-stats {\n flex-direction: column;\n gap: 0.5rem;\n margin-bottom: 1rem;\n }\n\n .app-content {\n padding: 0.75rem;\n }\n\n /* Entities grid in expanded view */\n .entities-grid {\n grid-template-columns: 1fr;\n gap: 0.5rem;\n }\n\n .entity-item {\n padding: 0.5rem;\n }\n\n .entity-item .entity-name {\n font-size: 0.875rem;\n word-break: break-word;\n }\n\n /* Loading container compact */\n .loading-container {\n padding: 2rem 1rem;\n }\n}\n\n/* Small mobile (< 375px) - Extra compact */\n@media (max-width: 374px) {\n .scrollable-content {\n padding: 0.375rem;\n }\n\n .app-header {\n padding: 0.625rem;\n }\n}\n"] }]
|
|
670
|
+
args: [{ standalone: false, selector: 'mj-application-management', template: "<!--\n Interior chrome \u2014 see Section 10 of explorer-chrome-conventions.\n-->\n<mj-page-header-interior\n AriaLabel=\"Filter applications\"\n Title=\"Applications\"\n Subtitle=\"Configure applications and their entity associations\">\n <!-- X-of-Y filtered count earns its meta spot per chrome conventions \u00A72.\n Partition badges (entities/public) stay dropped \u2014 Active/Inactive\n chips in [toolbar] handle the partition signal. -->\n <div meta>\n <mj-stat-badge\n [Count]=\"filteredApplications.length\"\n [Total]=\"applications.length\"\n Label=\"applications\">\n </mj-stat-badge>\n </div>\n <div toolbar>\n <!-- Concise-chrome control bar: search \u00B7 Filter. Status lives behind the\n one Filter button; applied filters show as removable chips below the\n card. On mobile the popover docks as a bottom sheet. -->\n <mj-page-search\n Placeholder=\"Search applications by name or description...\"\n [Value]=\"filters$.value.search\"\n (ValueChange)=\"updateFilter({ search: $event })\">\n </mj-page-search>\n <mj-filter-popover\n Label=\"Filters\"\n Icon=\"fa-solid fa-filter\"\n [ActiveCount]=\"TotalActiveFilterCount\"\n [ShowClearAll]=\"TotalActiveFilterCount > 0\"\n (ClearAllRequested)=\"clearAllAppliedFilters()\">\n <mj-filter-panel\n [Fields]=\"filterFields\"\n [Values]=\"filterValues\"\n (ValuesChange)=\"onFilterPanelChange($event)\"\n (Reset)=\"clearAllAppliedFilters()\">\n </mj-filter-panel>\n </mj-filter-popover>\n </div>\n <div actions>\n <mj-refresh-button [Loading]=\"isLoading\" (Clicked)=\"refreshData()\"></mj-refresh-button>\n <button mjButton variant=\"primary\" size=\"sm\" (click)=\"createNewApplication()\" aria-label=\"Add new application\">\n <i class=\"fa-solid fa-plus\"></i> <span class=\"mj-action-label\">Add Application</span>\n </button>\n </div>\n</mj-page-header-interior>\n\n<!-- Scrollable Content Section \u2014 owned by <mj-page-body-interior>. -->\n<mj-page-body-interior>\n <!-- Loading State -->\n @if (isLoading) {\n <div class=\"loading-container\" role=\"status\" aria-live=\"polite\">\n <mj-loading text=\"Loading applications...\"></mj-loading>\n </div>\n }\n\n <!-- Error State -->\n @if (error && !isLoading) {\n <div class=\"error-container\" role=\"alert\">\n <div class=\"error-content\">\n <i class=\"fa-solid fa-exclamation-triangle error-icon\" aria-hidden=\"true\"></i>\n <p class=\"error-message\">{{ error }}</p>\n <button mjButton variant=\"primary\" (click)=\"loadInitialData()\">\n <i class=\"fa-solid fa-refresh\"></i>\n Try Again\n </button>\n </div>\n </div>\n }\n\n <!-- Applications List -->\n @if (!isLoading && !error) {\n <div class=\"applications-list\" role=\"list\">\n @for (app of filteredApplications; track app.ID) {\n <div class=\"app-card\" [class.expanded]=\"isAppExpanded(app.ID)\" role=\"listitem\">\n <div class=\"app-header\" (click)=\"toggleAppExpansion(app.ID)\" role=\"button\" [attr.aria-expanded]=\"isAppExpanded(app.ID)\">\n <div class=\"app-info\">\n <div class=\"app-icon-wrapper\">\n <i [class]=\"'fa-solid ' + getAppIcon(app)\" aria-hidden=\"true\"></i>\n </div>\n <div class=\"app-details\">\n <h3 class=\"app-name\">{{ app.Name }}</h3>\n <p class=\"app-description\">{{ app.Description || 'No description available' }}</p>\n </div>\n </div>\n\n <div class=\"app-meta\">\n <span class=\"status-badge\" [class]=\"getAppStatusClass(app)\">\n {{ getAppStatusLabel(app) }}\n </span>\n <div class=\"app-actions\" (click)=\"$event.stopPropagation()\">\n <button\n mjButton variant=\"flat\" size=\"sm\"\n (click)=\"editApplication(app)\"\n title=\"Edit\"\n [attr.aria-label]=\"'Edit ' + app.Name\"\n >\n <i class=\"fa-solid fa-edit\"></i>\n </button>\n <button\n mjButton variant=\"danger\" size=\"sm\"\n (click)=\"confirmDeleteApplication(app)\"\n title=\"Delete\"\n [attr.aria-label]=\"'Delete ' + app.Name\"\n >\n <i class=\"fa-solid fa-trash\"></i>\n </button>\n </div>\n <button class=\"expand-btn\" aria-label=\"Toggle details\">\n <i class=\"fa-solid fa-chevron-down\" aria-hidden=\"true\"></i>\n </button>\n </div>\n </div>\n\n @if (isAppExpanded(app.ID)) {\n <div class=\"app-content\">\n <!-- Mobile-only actions bar -->\n <div class=\"mobile-actions-bar\" (click)=\"$event.stopPropagation()\">\n <span class=\"status-badge\" [class]=\"getAppStatusClass(app)\">\n {{ getAppStatusLabel(app) }}\n </span>\n <div class=\"mobile-action-buttons\">\n <button\n mjButton variant=\"flat\" size=\"sm\"\n (click)=\"editApplication(app)\"\n title=\"Edit\"\n [attr.aria-label]=\"'Edit ' + app.Name\"\n >\n <i class=\"fa-solid fa-edit\"></i>\n <span class=\"btn-label\">Edit</span>\n </button>\n <button\n mjButton variant=\"danger\" size=\"sm\"\n (click)=\"confirmDeleteApplication(app)\"\n title=\"Delete\"\n [attr.aria-label]=\"'Delete ' + app.Name\"\n >\n <i class=\"fa-solid fa-trash\"></i>\n <span class=\"btn-label\">Delete</span>\n </button>\n </div>\n </div>\n\n <div class=\"app-stats\">\n <div class=\"stat-item\">\n <i class=\"fa-solid fa-database\" aria-hidden=\"true\"></i>\n <span class=\"stat-label\">Entities:</span>\n <span class=\"stat-value\">{{ getAppEntities(app.ID).length }}</span>\n </div>\n <div class=\"stat-item\">\n <i class=\"fa-solid fa-calendar\" aria-hidden=\"true\"></i>\n <span class=\"stat-label\">Created:</span>\n <span class=\"stat-value\">{{ app.__mj_CreatedAt | date:'short' }}</span>\n </div>\n <div class=\"stat-item\">\n <i class=\"fa-solid fa-clock\" aria-hidden=\"true\"></i>\n <span class=\"stat-label\">Updated:</span>\n <span class=\"stat-value\">{{ app.__mj_UpdatedAt | date:'short' }}</span>\n </div>\n </div>\n\n <div class=\"entities-section\">\n <h4 class=\"section-title\">\n <i class=\"fa-solid fa-database\" aria-hidden=\"true\"></i>\n Application Entities\n </h4>\n\n @if (getAppEntities(app.ID).length > 0) {\n <div class=\"entities-grid\">\n @for (appEntity of getAppEntities(app.ID); track appEntity.ID) {\n @if (getEntityInfo(appEntity.EntityID); as entity) {\n <div class=\"entity-item\">\n <div class=\"entity-name\">{{ entity.DisplayNameOrName }}</div>\n <div class=\"entity-meta\">\n @if (appEntity.DefaultForNewUser) {\n <span class=\"entity-badge public\">Public</span>\n }\n <span class=\"entity-sequence\">Order: {{ appEntity.Sequence }}</span>\n </div>\n </div>\n }\n }\n </div>\n } @else {\n <p class=\"no-entities\">No entities configured for this application</p>\n }\n </div>\n </div>\n }\n </div>\n }\n\n @if (filteredApplications.length === 0) {\n <mj-empty-state\n [Variant]=\"IsListNarrowed ? 'no-results' : 'empty'\"\n Icon=\"fa-solid fa-cubes\"\n Title=\"No applications found\"\n Message=\"Try adjusting your filters or create a new application\"\n [ActionText]=\"IsListNarrowed ? 'Reset filters' : ''\"\n ActionIcon=\"fa-solid fa-rotate-left\"\n (Action)=\"resetAllFiltersAndSearch()\" />\n }\n </div>\n }\n </mj-page-body-interior>\n\n <!-- Application Dialog -->\n <mj-application-dialog\n [visible]=\"showApplicationDialog\"\n [data]=\"applicationDialogData\"\n (result)=\"onApplicationDialogResult($event)\"\n ></mj-application-dialog>\n\n <!-- Delete Confirmation Dialog -->\n @if (showDeleteConfirm && selectedApp) {\n <div class=\"modal-backdrop\" (click)=\"showDeleteConfirm = false\" role=\"presentation\">\n <div\n class=\"modal-dialog\"\n (click)=\"$event.stopPropagation()\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"delete-dialog-title\"\n aria-describedby=\"delete-dialog-description\"\n >\n <div class=\"modal-header\">\n <h3 class=\"modal-title\" id=\"delete-dialog-title\">\n <i class=\"fa-solid fa-exclamation-triangle text-danger\" aria-hidden=\"true\"></i>\n Confirm Delete\n </h3>\n <button\n class=\"modal-close\"\n (click)=\"showDeleteConfirm = false\"\n aria-label=\"Close dialog\"\n >\n <i class=\"fa-solid fa-times\" aria-hidden=\"true\"></i>\n </button>\n </div>\n <div class=\"modal-body\" id=\"delete-dialog-description\">\n <p>Are you sure you want to delete the application <strong>{{ selectedApp.Name }}</strong>?</p>\n <mj-alert Variant=\"warning\" Message=\"This will remove all entity associations for this application.\" />\n </div>\n <div class=\"modal-footer\">\n <!-- Primary action (Delete) on LEFT per MD3 design guide -->\n <button mjButton variant=\"danger\" (click)=\"deleteApplication()\" [disabled]=\"isLoading\">\n @if (isLoading) {\n <i class=\"fa-solid fa-spinner fa-spin\" aria-hidden=\"true\"></i>\n Deleting...\n } @else {\n <i class=\"fa-solid fa-trash\" aria-hidden=\"true\"></i>\n Delete Application\n }\n </button>\n <button mjButton (click)=\"showDeleteConfirm = false\">\n Cancel\n </button>\n </div>\n </div>\n </div>\n }\n", styles: ["/* =============================================================================\n Shared Admin Patterns - MJ Design System\n Common styles shared across all admin settings components.\n Component-specific CSS files override these values where needed.\n ============================================================================= */\n\n/* -----------------------------------------------------------------------------\n Host & Container\n ----------------------------------------------------------------------------- */\n:host {\n display: flex;\n flex-direction: column;\n height: 100%;\n width: 100%;\n overflow: hidden;\n}\n\n/* -----------------------------------------------------------------------------\n Sticky Header\n ----------------------------------------------------------------------------- */\n.sticky-header {\n flex-shrink: 0;\n background: var(--mj-bg-page);\n border-bottom: 1px solid var(--mj-border-default);\n box-shadow: var(--mj-shadow-sm);\n z-index: 10;\n}\n\n/* -----------------------------------------------------------------------------\n Scrollable Content\n ----------------------------------------------------------------------------- */\n.scrollable-content {\n flex: 1;\n overflow-y: auto;\n overflow-x: hidden;\n -webkit-overflow-scrolling: touch;\n padding: 1rem;\n background: var(--mj-bg-surface-sunken);\n}\n\n@media (min-width: 768px) {\n .scrollable-content {\n padding: 1.5rem 2rem;\n }\n}\n\n@media (min-width: 1024px) {\n .scrollable-content {\n padding: 2rem;\n }\n}\n\n@media (min-width: 1440px) {\n .scrollable-content {\n padding: 2rem 2.5rem;\n }\n}\n\n/* -----------------------------------------------------------------------------\n Action Buttons (In Sticky Header)\n ----------------------------------------------------------------------------- */\n.action-buttons {\n flex-shrink: 0;\n display: flex;\n gap: 0.75rem;\n justify-content: flex-end;\n padding: 0.75rem 1rem;\n background: var(--mj-bg-page);\n}\n\n@media (min-width: 768px) {\n .action-buttons {\n padding: 1rem 1.5rem;\n }\n}\n\n@media (max-width: 639px) {\n .action-buttons {\n justify-content: center;\n flex-wrap: wrap;\n }\n}\n\n/* -----------------------------------------------------------------------------\n Statistics Cards Grid - Static Display (Non-interactive)\n ----------------------------------------------------------------------------- */\n.mj-grid-4 {\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n gap: 0.5rem;\n padding: 0 1rem 0.75rem 1rem;\n background: var(--mj-bg-page);\n}\n\n@media (min-width: 768px) {\n .mj-grid-4 {\n grid-template-columns: repeat(4, 1fr);\n padding: 0 1.5rem 1rem 1.5rem;\n gap: 0.75rem;\n }\n}\n\n@media (min-width: 1024px) {\n .mj-grid-4 {\n gap: 1rem;\n }\n}\n\n/* Static Card - No hover effects */\n.mj-card {\n background: var(--mj-bg-surface-card);\n border-radius: var(--mj-radius-lg);\n padding: 1rem;\n box-shadow: var(--mj-shadow-sm);\n display: flex;\n align-items: center;\n gap: 1rem;\n border: 1px solid var(--mj-border-default);\n cursor: default;\n pointer-events: none;\n}\n\n@media (min-width: 768px) {\n .mj-card {\n padding: 1rem 1.25rem;\n }\n}\n\n/* -----------------------------------------------------------------------------\n Stat Icon Containers\n ----------------------------------------------------------------------------- */\n.stat-icon {\n width: 48px;\n height: 48px;\n border-radius: var(--mj-radius-lg);\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 1.25rem;\n flex-shrink: 0;\n}\n\n@media (min-width: 768px) {\n .stat-icon {\n width: 52px;\n height: 52px;\n font-size: 1.375rem;\n }\n}\n\n.stat-icon-total {\n background: var(--mj-color-accent-300);\n color: var(--mj-brand-primary);\n}\n\n.stat-icon-active {\n background: var(--mj-color-success-100);\n color: var(--mj-status-success);\n}\n\n/* -----------------------------------------------------------------------------\n Stat Content Typography\n ----------------------------------------------------------------------------- */\n.stat-content {\n flex: 1;\n min-width: 0;\n}\n\n.stat-content .stat-value {\n font-size: 1.75rem;\n font-weight: 700;\n color: var(--mj-text-primary);\n line-height: 1;\n letter-spacing: -0.02em;\n}\n\n@media (min-width: 768px) {\n .stat-content .stat-value {\n font-size: 2rem;\n }\n}\n\n.stat-content .stat-label {\n font-size: 0.75rem;\n font-weight: 500;\n color: var(--mj-text-secondary);\n text-transform: uppercase;\n letter-spacing: 0.05em;\n margin-top: 0.25rem;\n}\n\n@media (min-width: 768px) {\n .stat-content .stat-label {\n font-size: 0.8125rem;\n }\n}\n\n/* -----------------------------------------------------------------------------\n Filters Section\n ----------------------------------------------------------------------------- */\n.filters-section {\n flex-shrink: 0;\n background: var(--mj-bg-surface-card);\n margin: 0 1rem 0.75rem 1rem;\n padding: 0.75rem;\n border-radius: var(--mj-radius-lg);\n border: 1px solid var(--mj-border-default);\n}\n\n@media (min-width: 768px) {\n .filters-section {\n margin: 0 1.5rem 1rem 1.5rem;\n padding: 1rem 1.25rem;\n }\n}\n\n.filters-row {\n display: flex;\n gap: 1rem;\n align-items: flex-end;\n flex-wrap: wrap;\n}\n\n@media (min-width: 768px) {\n .filters-row {\n gap: 1.5rem;\n }\n}\n\n.mj-filter-group {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n\n.mj-filter-label {\n font-size: 0.75rem;\n font-weight: 500;\n color: var(--mj-text-primary);\n text-transform: uppercase;\n letter-spacing: 0.05em;\n}\n\n.mj-filter-buttons {\n display: flex;\n background: var(--mj-bg-surface-sunken);\n border-radius: var(--mj-radius-full);\n padding: 4px;\n gap: 2px;\n}\n\n/* -----------------------------------------------------------------------------\n Search Input\n ----------------------------------------------------------------------------- */\n.mj-search {\n position: relative;\n flex: 1;\n min-width: 200px;\n}\n\n@media (min-width: 640px) {\n .mj-search {\n min-width: 280px;\n }\n}\n\n@media (min-width: 1024px) {\n .mj-search {\n min-width: 400px;\n max-width: 600px;\n }\n}\n\n@media (min-width: 1440px) {\n .mj-search {\n min-width: 500px;\n max-width: 800px;\n }\n}\n\n.mj-search .mj-search-icon {\n position: absolute;\n left: 1rem;\n top: 50%;\n transform: translateY(-50%);\n color: var(--mj-text-secondary);\n font-size: 1rem;\n pointer-events: none;\n transition: color 0.2s ease;\n}\n\n@media (min-width: 1024px) {\n .mj-search .mj-search-icon {\n left: 1.25rem;\n font-size: 1.125rem;\n }\n}\n\n.mj-search .mj-search-input {\n width: 100%;\n padding: 0.875rem 1rem 0.875rem 2.75rem;\n border: 2px solid var(--mj-border-default);\n border-radius: var(--mj-radius-full);\n font-size: 1rem;\n background: var(--mj-bg-page);\n color: var(--mj-text-primary);\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n min-height: 44px;\n}\n\n@media (min-width: 1024px) {\n .mj-search .mj-search-input {\n padding: 1rem 1.25rem 1rem 3.25rem;\n font-size: 1.0625rem;\n }\n}\n\n@media (min-width: 1440px) {\n .mj-search .mj-search-input {\n padding: 1.125rem 1.5rem 1.125rem 3.5rem;\n }\n}\n\n.mj-search .mj-search-input::placeholder {\n color: var(--mj-text-secondary);\n}\n\n.mj-search .mj-search-input:hover {\n border-color: var(--mj-brand-primary);\n background: var(--mj-bg-surface);\n}\n\n.mj-search .mj-search-input:focus {\n outline: none;\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 3px rgba(0, 118, 182, 0.2);\n background: var(--mj-bg-page);\n}\n\n.mj-search:focus-within .mj-search-icon {\n color: var(--mj-brand-primary);\n}\n\n/* -----------------------------------------------------------------------------\n Content Area\n ----------------------------------------------------------------------------- */\n.content-area {\n flex: 1 1 auto;\n overflow: visible;\n position: relative;\n background: var(--mj-bg-page);\n border-radius: var(--mj-radius-xl);\n box-shadow: var(--mj-shadow-sm);\n padding: 1rem;\n border: 1px solid var(--mj-border-default);\n}\n\n@media (min-width: 768px) {\n .content-area {\n padding: 1.5rem;\n }\n}\n\n/* -----------------------------------------------------------------------------\n Expand Button\n ----------------------------------------------------------------------------- */\n.expand-btn {\n padding: 0.625rem;\n border: none;\n background: transparent;\n color: var(--mj-text-secondary);\n font-size: 1rem;\n cursor: pointer;\n transition: all 0.2s ease;\n border-radius: var(--mj-radius-full);\n min-width: 44px;\n min-height: 44px;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n}\n\n.expand-btn:hover {\n background: var(--mj-brand-primary);\n color: var(--mj-brand-on-primary);\n}\n\n.expand-btn i {\n transition: transform 0.3s cubic-bezier(0, 0, 0.2, 1);\n}\n\n/* -----------------------------------------------------------------------------\n Status Badge\n ----------------------------------------------------------------------------- */\n.status-badge {\n display: inline-flex;\n align-items: center;\n gap: 0.375rem;\n padding: 0.375rem 0.875rem;\n border-radius: var(--mj-radius-full);\n font-size: 0.75rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.03em;\n}\n\n.status-badge.status-active {\n background: var(--mj-color-success-100);\n color: var(--mj-color-success-800);\n border: 1px solid var(--mj-status-success);\n}\n\n.status-badge.status-inactive {\n background: var(--mj-color-error-100);\n color: var(--mj-status-error);\n border: 1px solid var(--mj-status-error);\n}\n\n.status-badge i {\n font-size: 0.625rem;\n}\n\n/* -----------------------------------------------------------------------------\n Mobile Actions Bar\n ----------------------------------------------------------------------------- */\n.mobile-actions-bar {\n display: none;\n}\n\n.mobile-action-buttons {\n display: flex;\n gap: 0.5rem;\n}\n\n/* -----------------------------------------------------------------------------\n Empty State\n ----------------------------------------------------------------------------- */\n.empty-state {\n text-align: center;\n padding: 4rem 2rem;\n}\n\n.empty-state .empty-icon {\n font-size: 4rem;\n color: var(--mj-border-default);\n margin-bottom: 1.5rem;\n}\n\n.empty-state .empty-text {\n font-size: 1.25rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin: 0 0 0.5rem 0;\n}\n\n.empty-state .empty-subtext {\n font-size: 1rem;\n color: var(--mj-text-secondary);\n margin: 0;\n}\n\n/* -----------------------------------------------------------------------------\n Loading State\n ----------------------------------------------------------------------------- */\n.loading-container {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 4rem 2rem;\n}\n\n/* -----------------------------------------------------------------------------\n Error State\n ----------------------------------------------------------------------------- */\n.error-container {\n text-align: center;\n padding: 4rem 2rem;\n}\n\n.error-container .error-content {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 1rem;\n}\n\n.error-container .error-icon {\n font-size: 3.5rem;\n color: var(--mj-status-error);\n margin-bottom: 1rem;\n}\n\n.error-container .error-message {\n font-size: 1.0625rem;\n color: var(--mj-text-primary);\n margin: 0 0 1.5rem 0;\n}\n\n/* -----------------------------------------------------------------------------\n Modal Dialog\n ----------------------------------------------------------------------------- */\n.modal-backdrop {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.4);\n backdrop-filter: blur(4px);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1000;\n padding: 1rem;\n animation: fadeIn 0.2s cubic-bezier(0, 0, 0.2, 1);\n}\n\n.modal-dialog {\n background: var(--mj-bg-page);\n border-radius: var(--mj-radius-2xl);\n box-shadow: var(--mj-shadow-2xl);\n max-width: 500px;\n width: 100%;\n max-height: 90vh;\n overflow: hidden;\n animation: slideUp 0.3s cubic-bezier(0, 0, 0.2, 1);\n}\n\n.modal-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 1.5rem;\n border-bottom: 1px solid var(--mj-border-default);\n}\n\n.modal-header .modal-title {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n font-size: 1.375rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin: 0;\n}\n\n.modal-header .modal-close {\n padding: 0.625rem;\n border: none;\n background: transparent;\n color: var(--mj-text-secondary);\n font-size: 1.25rem;\n cursor: pointer;\n border-radius: var(--mj-radius-full);\n transition: all 0.2s ease;\n min-width: 44px;\n min-height: 44px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.modal-header .modal-close:hover {\n background: var(--mj-brand-primary);\n color: var(--mj-brand-on-primary);\n}\n\n.modal-body {\n padding: 1.5rem;\n}\n\n.modal-body p {\n font-size: 1rem;\n color: var(--mj-text-primary);\n margin: 0 0 1rem 0;\n line-height: 1.5;\n}\n\n.modal-body p:last-child {\n margin-bottom: 0;\n}\n\n.modal-footer {\n display: flex;\n justify-content: flex-start;\n gap: 0.75rem;\n padding: 1.5rem;\n border-top: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-sunken);\n}\n\n@media (max-width: 639px) {\n .modal-dialog {\n width: 95%;\n max-height: 85vh;\n }\n\n .modal-header {\n padding: 1rem;\n }\n\n .modal-header .modal-title {\n font-size: 1.125rem;\n }\n\n .modal-body {\n padding: 1rem;\n }\n\n .modal-footer {\n padding: 1rem;\n flex-direction: column;\n }\n\n}\n\n/* The legacy \"MJ Button System\" rule block (.mj-btn / .mj-btn-primary /\n .mj-btn-secondary / .mj-btn-ghost / .mj-btn-danger / .mj-btn-sm /\n .mj-btn-icon-only / .mj-btn-icon-mobile) was removed 2026-05-19. These\n rules pre-dated the mjButton directive standardization (the directive\n uses BEM modifiers like .mj-btn--primary, single-dash names like\n .mj-btn-primary don't match it anyway). The base .mj-btn rule WAS active\n though \u2014 its component-scoped specificity outranked button.scss's global\n directive styles, so admin sub-page buttons rendered pill-shaped + 44px\n regardless of the variant/size requested via mjButton inputs. Removing the\n rule lets every sub-page fall back to the directive's intended look. */\n\n/* -----------------------------------------------------------------------------\n Filter Button (Mobile Only)\n ----------------------------------------------------------------------------- */\n.filter-button {\n display: none;\n align-items: center;\n justify-content: center;\n gap: 0.5rem;\n padding: 0.75rem 1.25rem;\n border: 2px solid var(--mj-border-default);\n background: var(--mj-bg-page);\n color: var(--mj-text-primary);\n border-radius: var(--mj-radius-full);\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 min-height: 44px;\n min-width: 44px;\n}\n\n.filter-button:hover {\n border-color: var(--mj-brand-primary);\n background: var(--mj-bg-surface);\n}\n\n.filter-button:active {\n background: var(--mj-brand-primary);\n color: var(--mj-brand-on-primary);\n border-color: var(--mj-brand-primary);\n}\n\n.filter-button i {\n font-size: 1rem;\n}\n\n/* -----------------------------------------------------------------------------\n Filter Modal (Mobile Bottom Sheet)\n ----------------------------------------------------------------------------- */\n.filter-modal-backdrop {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.4);\n backdrop-filter: blur(4px);\n display: flex;\n align-items: flex-end;\n justify-content: center;\n z-index: 1000;\n animation: fadeIn 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n padding: 0;\n}\n\n.filter-modal {\n background: var(--mj-bg-page);\n border-radius: var(--mj-radius-2xl) var(--mj-radius-2xl) 0 0;\n box-shadow: var(--mj-shadow-2xl);\n width: 100%;\n max-height: 80vh;\n overflow: hidden;\n animation: slideUpFromBottom 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n display: flex;\n flex-direction: column;\n}\n\n.filter-modal-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 1.25rem 1.5rem;\n border-bottom: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n}\n\n.filter-modal-title {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n font-size: 1.25rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin: 0;\n}\n\n.filter-modal-title i {\n color: var(--mj-brand-primary);\n font-size: 1.375rem;\n}\n\n.filter-modal-close {\n padding: 0.625rem;\n border: none;\n background: transparent;\n color: var(--mj-text-secondary);\n font-size: 1.25rem;\n cursor: pointer;\n border-radius: var(--mj-radius-full);\n transition: all 0.2s ease;\n min-width: 44px;\n min-height: 44px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.filter-modal-close:hover {\n background: var(--mj-brand-primary);\n color: var(--mj-brand-on-primary);\n}\n\n.filter-modal-body {\n padding: 1.5rem;\n overflow-y: auto;\n -webkit-overflow-scrolling: touch;\n flex: 1;\n}\n\n.filter-modal-footer {\n display: flex;\n gap: 0.75rem;\n padding: 1.25rem 1.5rem;\n border-top: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-sunken);\n flex-shrink: 0;\n}\n\n.filter-modal-footer button {\n flex: 1;\n margin: 0;\n}\n\n/* Filter Options Container (Inside Modal) */\n.filter-options-container {\n display: flex;\n flex-direction: column;\n gap: 1.5rem;\n}\n\n.filter-group {\n display: flex;\n flex-direction: column;\n gap: 0.75rem;\n}\n\n.filter-group-label {\n font-size: 0.875rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n text-transform: uppercase;\n letter-spacing: 0.05em;\n margin: 0;\n}\n\n.filter-group-options {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n\n.filter-option {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n padding: 0.875rem 1rem;\n background: var(--mj-bg-surface-sunken);\n border: 2px solid var(--mj-border-default);\n border-radius: var(--mj-radius-lg);\n cursor: pointer;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n min-height: 56px;\n}\n\n.filter-option:hover {\n border-color: var(--mj-brand-primary);\n background: var(--mj-bg-surface-sunken);\n}\n\n.filter-option.selected {\n background: var(--mj-color-accent-300);\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-primary);\n}\n\n.filter-option input[type=\"checkbox\"],\n.filter-option input[type=\"radio\"] {\n width: 20px;\n height: 20px;\n cursor: pointer;\n accent-color: var(--mj-brand-primary);\n flex-shrink: 0;\n}\n\n.filter-option-label {\n flex: 1;\n font-size: 1rem;\n font-weight: 500;\n color: var(--mj-text-primary);\n}\n\n.filter-option.selected .filter-option-label {\n color: var(--mj-brand-primary);\n font-weight: 600;\n}\n\n/* -----------------------------------------------------------------------------\n Utility Classes\n ----------------------------------------------------------------------------- */\n.text-danger {\n color: var(--mj-status-error);\n}\n\n.text-warning {\n color: var(--mj-color-warning-500);\n}\n\n.text-success {\n color: var(--mj-status-success);\n}\n\n/* -----------------------------------------------------------------------------\n Animations\n ----------------------------------------------------------------------------- */\n@keyframes fadeIn {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n\n@keyframes slideUp {\n from {\n transform: translateY(20px);\n opacity: 0;\n }\n to {\n transform: translateY(0);\n opacity: 1;\n }\n}\n\n@keyframes slideDown {\n from {\n opacity: 0;\n transform: translateY(-10px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n@keyframes slideUpFromBottom {\n from {\n transform: translateY(100%);\n opacity: 0;\n }\n to {\n transform: translateY(0);\n opacity: 1;\n }\n}\n\n/* -----------------------------------------------------------------------------\n Accessibility: Focus Indicators\n ----------------------------------------------------------------------------- */\n.mj-search-input:focus-visible,\nbutton: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 Application Management Component - Component-Specific Styles\n Shared patterns are in ../shared/styles/_admin-patterns.css\n ============================================================================= */\n\n/* :host already provides flex column / 100% height / overflow hidden via\n _admin-patterns.css. No bespoke container wrapper \u2014 see Section 10 of\n explorer-chrome-conventions. */\n\n/* Status chip group inside <mj-page-header-interior>'s [toolbar] slot. */\n.status-chips {\n display: inline-flex;\n align-items: center;\n gap: var(--mj-space-2);\n flex-shrink: 0;\n}\n\n/* -----------------------------------------------------------------------------\n Stat Icon Overrides (Component-specific colors)\n ----------------------------------------------------------------------------- */\n.stat-icon-entities {\n background: var(--mj-color-warning-50);\n color: var(--mj-color-warning-700);\n}\n\n.stat-icon-public {\n background: var(--mj-color-success-100);\n color: var(--mj-status-success);\n}\n\n/* -----------------------------------------------------------------------------\n Applications List\n ----------------------------------------------------------------------------- */\n.applications-list {\n display: flex;\n flex-direction: column;\n gap: 1rem;\n}\n\n/* -----------------------------------------------------------------------------\n Application Card - Interactive\n ----------------------------------------------------------------------------- */\n.app-card {\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-xl);\n overflow: hidden;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n cursor: pointer;\n}\n\n.app-card:hover {\n box-shadow: var(--mj-shadow-md);\n border-color: var(--mj-brand-primary);\n}\n\n.app-card.expanded {\n box-shadow: var(--mj-shadow-lg);\n border-color: var(--mj-brand-primary);\n}\n\n.app-card.expanded .expand-btn i {\n transform: rotate(180deg);\n}\n\n.app-header {\n padding: 1.25rem 1.5rem;\n display: flex;\n justify-content: space-between;\n align-items: center;\n cursor: pointer;\n background: var(--mj-bg-surface);\n transition: background-color 0.2s ease;\n}\n\n.app-header:hover {\n background: var(--mj-bg-surface-sunken);\n}\n\n.app-info {\n display: flex;\n align-items: center;\n gap: 1rem;\n flex: 1;\n min-width: 0;\n}\n\n.app-icon-wrapper {\n width: 48px;\n height: 48px;\n border-radius: var(--mj-radius-lg);\n background: var(--mj-color-accent-300);\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-brand-primary);\n font-size: 1.25rem;\n flex-shrink: 0;\n}\n\n.app-details {\n flex: 1;\n min-width: 0;\n}\n\n.app-details .app-name {\n font-size: 1.0625rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin: 0 0 0.25rem 0;\n word-break: break-word;\n overflow-wrap: break-word;\n}\n\n.app-details .app-description {\n font-size: 0.9375rem;\n color: var(--mj-text-secondary);\n margin: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n@media (max-width: 639px) {\n .app-details .app-description {\n white-space: normal;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n }\n}\n\n.app-meta {\n display: flex;\n align-items: center;\n gap: 1rem;\n flex-shrink: 0;\n}\n\n/* -----------------------------------------------------------------------------\n Application Actions\n ----------------------------------------------------------------------------- */\n.app-actions {\n display: flex;\n gap: 0.5rem;\n}\n\n/* -----------------------------------------------------------------------------\n Application Content (Expanded)\n ----------------------------------------------------------------------------- */\n.app-content {\n padding: 1.5rem;\n background: var(--mj-bg-page);\n border-top: 1px solid var(--mj-border-default);\n animation: slideDown 0.3s cubic-bezier(0, 0, 0.2, 1);\n}\n\n.app-stats {\n display: flex;\n gap: 2rem;\n margin-bottom: 1.5rem;\n flex-wrap: wrap;\n}\n\n.app-stats .stat-item {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n font-size: 0.9375rem;\n}\n\n.app-stats .stat-item i {\n color: var(--mj-text-secondary);\n}\n\n.app-stats .stat-item .stat-label {\n color: var(--mj-text-secondary);\n}\n\n.app-stats .stat-item .stat-value {\n color: var(--mj-text-primary);\n font-weight: 600;\n}\n\n/* -----------------------------------------------------------------------------\n Entities Section\n ----------------------------------------------------------------------------- */\n.entities-section {\n padding: 1rem;\n background: var(--mj-bg-surface-sunken);\n border-radius: var(--mj-radius-md);\n}\n\n.entities-section .section-title {\n display: flex;\n align-items: center;\n gap: 0.625rem;\n font-size: 1rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin: 0 0 1rem 0;\n}\n\n.entities-section .section-title i {\n color: var(--mj-brand-primary);\n font-size: 1.125rem;\n}\n\n.entities-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));\n gap: 0.75rem;\n}\n\n@media (min-width: 640px) {\n .entities-grid {\n grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));\n }\n}\n\n.entity-item {\n padding: 0.75rem;\n background: var(--mj-bg-page);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md);\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.entity-item:hover {\n border-color: var(--mj-brand-primary);\n box-shadow: var(--mj-shadow-sm);\n}\n\n.entity-item .entity-name {\n font-size: 0.9375rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin-bottom: 0.25rem;\n}\n\n.entity-item .entity-meta {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n font-size: 0.8125rem;\n color: var(--mj-text-secondary);\n}\n\n.entity-badge {\n padding: 0.125rem 0.5rem;\n border-radius: var(--mj-radius-full);\n font-size: 0.6875rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.03em;\n}\n\n.entity-badge.public {\n background: var(--mj-color-success-100);\n color: var(--mj-color-success-800);\n border: 1px solid var(--mj-status-success);\n}\n\n.entity-sequence {\n font-size: 0.8125rem;\n color: var(--mj-text-secondary);\n}\n\n.no-entities {\n color: var(--mj-text-secondary);\n font-size: 0.9375rem;\n text-align: center;\n padding: 1rem;\n margin: 0;\n}\n\n/* -----------------------------------------------------------------------------\n Modal Body Override - warning callout\n ----------------------------------------------------------------------------- */\n.modal-body p {\n font-size: 1.0625rem;\n}\n\n\n/* -----------------------------------------------------------------------------\n Desktop (1440px+) - Large Screen Overrides\n ----------------------------------------------------------------------------- */\n@media (min-width: 1440px) {\n .application-management-container {\n margin: 0 auto;\n }\n\n .mj-grid-4 {\n padding: 0 2rem 1rem 2rem;\n gap: 1.25rem;\n }\n}\n\n/* -----------------------------------------------------------------------------\n Mobile-specific adjustments (< 640px)\n ----------------------------------------------------------------------------- */\n@media (max-width: 639px) {\n /* Hide stats cards on mobile to save space */\n .mj-grid-4 {\n display: none !important;\n }\n\n /* More compact scrollable content */\n .scrollable-content {\n padding: 0.5rem;\n }\n\n /* Modal styles */\n .modal-backdrop {\n padding: 0.5rem;\n }\n\n .modal-dialog {\n border-radius: var(--mj-radius-2xl);\n max-height: 95vh;\n }\n\n /* App Card Mobile Layout */\n .applications-list {\n gap: 0.5rem;\n }\n\n .app-card {\n border-radius: var(--mj-radius-md);\n overflow: hidden;\n }\n\n .app-header {\n flex-direction: row;\n align-items: center;\n justify-content: space-between;\n gap: 0.5rem;\n padding: 0.75rem;\n }\n\n .app-info {\n flex: 1;\n min-width: 0;\n flex-direction: row;\n align-items: flex-start;\n gap: 0.75rem;\n }\n\n .app-icon-wrapper {\n width: 36px;\n height: 36px;\n font-size: 0.875rem;\n flex-shrink: 0;\n border-radius: var(--mj-radius-md);\n }\n\n .app-details {\n flex: 1;\n min-width: 0;\n }\n\n .app-details .app-name {\n font-size: 0.9375rem;\n line-height: 1.3;\n margin-bottom: 0.125rem;\n }\n\n .app-details .app-description {\n font-size: 0.8125rem;\n -webkit-line-clamp: 1;\n line-clamp: 1;\n }\n\n /* On mobile: hide status and actions in header, show only expand button */\n .app-meta {\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: flex-end;\n padding-top: 0;\n border-top: none;\n width: auto;\n }\n\n .app-meta .status-badge {\n display: none;\n }\n\n .app-meta .app-actions {\n display: none;\n }\n\n .expand-btn {\n flex-shrink: 0;\n min-width: 36px;\n min-height: 36px;\n padding: 0.5rem;\n border-radius: var(--mj-radius-full);\n }\n\n .expand-btn i {\n font-size: 1rem;\n }\n\n /* Mobile actions bar - visible on mobile only */\n .mobile-actions-bar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 0.75rem;\n padding: 0.75rem;\n background: var(--mj-bg-surface-sunken);\n border-radius: var(--mj-radius-md);\n margin-bottom: 0.75rem;\n }\n\n .mobile-actions-bar .status-badge {\n padding: 0.25rem 0.625rem;\n font-size: 0.6875rem;\n }\n\n .mobile-action-buttons {\n display: flex;\n gap: 0.5rem;\n }\n\n .mobile-action-buttons .mj-btn-sm {\n display: flex;\n align-items: center;\n gap: 0.375rem;\n padding: 0.5rem 0.875rem;\n min-height: 36px;\n border-radius: var(--mj-radius-full);\n }\n\n .mobile-action-buttons .mj-btn-sm i {\n font-size: 0.875rem;\n }\n\n .mobile-action-buttons .btn-label {\n font-size: 0.8125rem;\n }\n\n .app-stats {\n flex-direction: column;\n gap: 0.5rem;\n margin-bottom: 1rem;\n }\n\n .app-content {\n padding: 0.75rem;\n }\n\n /* Entities grid in expanded view */\n .entities-grid {\n grid-template-columns: 1fr;\n gap: 0.5rem;\n }\n\n .entity-item {\n padding: 0.5rem;\n }\n\n .entity-item .entity-name {\n font-size: 0.875rem;\n word-break: break-word;\n }\n\n /* Loading container compact */\n .loading-container {\n padding: 2rem 1rem;\n }\n}\n\n/* Small mobile (< 375px) - Extra compact */\n@media (max-width: 374px) {\n .scrollable-content {\n padding: 0.375rem;\n }\n\n .app-header {\n padding: 0.625rem;\n }\n}\n"] }]
|
|
671
671
|
}], () => [{ type: i0.ChangeDetectorRef }, { type: i0.NgZone }], null); })();
|
|
672
672
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(ApplicationManagementComponent, { className: "ApplicationManagementComponent", filePath: "src/lib/application-management/application-management.component.ts", lineNumber: 30 }); })();
|
|
673
673
|
//# sourceMappingURL=application-management.component.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"application-management.component.js","sourceRoot":"","sources":["../../../src/lib/application-management/application-management.component.ts","../../../src/lib/application-management/application-management.component.html"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAqB,SAAS,EAAqB,MAAM,eAAe,CAAC;AAChF,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,MAAM,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAC/E,OAAO,EAAE,OAAO,EAAY,MAAM,sBAAsB,CAAC;AAEzD,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAG,UAAU,EAAE,MAAM,wBAAwB,CAAC;;;;;;;;IC8ChE,+BAAgE;IAC9D,iCAAwD;IAC1D,iBAAM;;;;IAMJ,AADF,+BAA0C,cACb;IACzB,wBAA8E;IAC9E,6BAAyB;IAAA,YAAW;IAAA,iBAAI;IACxC,kCAA+D;IAA5B,mMAAS,wBAAiB,KAAC;IAC5D,wBAAmC;IACnC,2BACF;IAEJ,AADE,AADE,iBAAS,EACL,EACF;;;IANuB,eAAW;IAAX,kCAAW;;;IAkHZ,gCAAkC;IAAA,sBAAM;IAAA,iBAAO;;;IAHnD,AADF,+BAAyB,cACE;IAAA,YAAiB;IAAA,iBAAM;IAChD,+BAAyB;IACvB,yKAAmC;IAGnC,gCAA8B;IAAA,YAA+B;IAEjE,AADE,AAD+D,iBAAO,EAChE,EACF;;;IAPqB,eAAiB;IAAjB,8BAAiB;IAExC,eAEC;IAFD,yDAEC;IAC6B,eAA+B;IAA/B,uDAA+B;;;IAPnE,0JAAoD;;;;;IAApD,6FAUC;;;IAZL,+BAA2B;IACzB,sJAYC;IACH,iBAAM;;;;IAbJ,cAYC;IAZD,cAAA,gCAAsB,CAYrB;;;IAGH,6BAAuB;IAAA,2DAA2C;IAAA,iBAAI;;;;IAnE1E,AAFF,+BAAyB,cAE4C;IAAnC,kJAAS,wBAAwB,IAAC;IAChE,gCAA4D;IAC1D,YACF;IAAA,iBAAO;IAEL,AADF,+BAAmC,iBAMhC;IAHC,sQAAS,8BAAoB,KAAC;IAI9B,wBAAgC;IAChC,gCAAwB;IAAA,oBAAI;IAC9B,AAD8B,iBAAO,EAC5B;IACT,kCAKC;IAHC,sQAAS,uCAA6B,KAAC;IAIvC,yBAAiC;IACjC,iCAAwB;IAAA,uBAAM;IAGpC,AADE,AADE,AADgC,iBAAO,EAC9B,EACL,EACF;IAGJ,AADF,gCAAuB,eACE;IACrB,yBAAuD;IACvD,iCAAyB;IAAA,0BAAS;IAAA,iBAAO;IACzC,iCAAyB;IAAA,aAAmC;IAC9D,AAD8D,iBAAO,EAC/D;IACN,gCAAuB;IACrB,yBAAuD;IACvD,iCAAyB;IAAA,yBAAQ;IAAA,iBAAO;IACxC,iCAAyB;IAAA,aAAuC;;IAClE,AADkE,iBAAO,EACnE;IACN,gCAAuB;IACrB,yBAAoD;IACpD,iCAAyB;IAAA,yBAAQ;IAAA,iBAAO;IACxC,iCAAyB;IAAA,aAAuC;;IAEpE,AADE,AADkE,iBAAO,EACnE,EACF;IAGJ,AADF,gCAA8B,cACF;IACxB,yBAAuD;IACvD,uCACF;IAAA,iBAAK;IAkBH,AAhBF,uIAAyC,+GAgBhC;IAIb,AADE,iBAAM,EACF;;;;IArEyB,eAAgC;IAAhC,+CAAgC;IACzD,cACF;IADE,iEACF;IAMI,eAAsC;;IAStC,eAAwC;;IAYjB,gBAAmC;IAAnC,6DAAmC;IAKnC,eAAuC;IAAvC,2EAAuC;IAKvC,eAAuC;IAAvC,4EAAuC;IAUlE,eAkBC;IAlBD,uEAkBC;;;;IA9GP,AADF,+BAA+E,cAC2C;IAAhG,gOAAS,oCAA0B,KAAC;IAExD,AADF,+BAAsB,cACU;IAC5B,wBAAkE;IACpE,iBAAM;IAEJ,AADF,+BAAyB,aACF;IAAA,YAAc;IAAA,iBAAK;IACxC,6BAA2B;IAAA,YAAmD;IAElF,AADE,AADgF,iBAAI,EAC9E,EACF;IAGJ,AADF,gCAAsB,gBACwC;IAC1D,aACF;IAAA,iBAAO;IACP,gCAA4D;IAAnC,oIAAS,wBAAwB,IAAC;IACzD,mCAKC;IAHC,oOAAS,8BAAoB,KAAC;IAI9B,yBAAgC;IAClC,iBAAS;IACT,mCAKC;IAHC,oOAAS,uCAA6B,KAAC;IAIvC,yBAAiC;IAErC,AADE,iBAAS,EACL;IACN,mCAAuD;IACrD,yBAA2D;IAGjE,AADE,AADE,iBAAS,EACL,EACF;IAEN,0HAA6B;IA2E/B,iBAAM;;;;IAnHgB,2DAAwC;IACe,cAA4C;;IAG9G,eAAuC;IAAvC,sDAAuC;IAGrB,eAAc;IAAd,iCAAc;IACR,eAAmD;IAAnD,sEAAmD;IAKrD,eAAgC;IAAhC,+CAAgC;IACzD,cACF;IADE,iEACF;IAMI,eAAsC;;IAQtC,eAAwC;;IAWhD,eA0EC;IA1ED,2DA0EC;;;;IAKH,0CAO0C;IAAxC,4NAAU,iCAA0B,KAAC;IAPvC,iBAO0C;;;IAFxC,AAJA,wEAAmD,4DAIC;;;IA9H5D,+BAA2C;IACvC,mHAqHC;IAED,2HAAyC;IAU7C,iBAAM;;;IAjIF,cAqHC;IArHD,0CAqHC;IAED,eASC;IATD,mEASC;;;IA4CK,wBAA8D;IAC9D,6BACF;;;IACE,wBAAoD;IACpD,oCACF;;;;IAnCR,+BAAoF;IAAxD,2NAA6B,KAAK,KAAC;IAC7D,+BAOC;IALC,6HAAS,wBAAwB,IAAC;IAOhC,AADF,+BAA0B,aACyB;IAC/C,wBAA+E;IAC/E,gCACF;IAAA,iBAAK;IACL,kCAIC;IAFC,8NAA6B,KAAK,KAAC;IAGnC,wBAAoD;IAExD,AADE,iBAAS,EACL;IAEJ,AADF,+BAAuD,QAClD;IAAA,iEAAgD;IAAA,+BAAQ;IAAA,aAAsB;IAAA,iBAAS;IAAA,kBAAC;IAAA,iBAAI;IAC/F,gCAAuG;IACzG,iBAAM;IAGJ,AAFF,gCAA0B,kBAE+D;IAArD,oMAAS,0BAAmB,KAAC;IAI3D,AAHF,uGAAiB,iFAGR;IAIX,iBAAS;IACT,mCAAqD;IAApC,+NAA6B,KAAK,KAAC;IAClD,yBACF;IAGN,AADE,AADE,AADE,iBAAS,EACL,EACF,EACF;;;IAnB2D,gBAAsB;IAAtB,6CAAsB;IAKjB,eAAsB;IAAtB,2CAAsB;IACpF,cAMC;IAND,4CAMC;;AD9NN,IAAM,8BAA8B,GAApC,MAAM,8BAA+B,SAAQ,aAAa;IAiC3C;IAAgC;IAhCpD,mBAAmB;IACZ,YAAY,GAA0B,EAAE,CAAC;IACzC,oBAAoB,GAA0B,EAAE,CAAC;IACjD,WAAW,GAA+B,IAAI,CAAC;IAC/C,SAAS,GAAG,KAAK,CAAC;IAClB,KAAK,GAAkB,IAAI,CAAC;IAEnC,+BAA+B;IACxB,WAAW,GAA6C,IAAI,GAAG,EAAE,CAAC;IAEzE,QAAQ;IACD,KAAK,GAAa;QACvB,iBAAiB,EAAE,CAAC;QACpB,kBAAkB,EAAE,CAAC;QACrB,aAAa,EAAE,CAAC;QAChB,cAAc,EAAE,CAAC;KAClB,CAAC;IAEF,UAAU;IACH,QAAQ,GAAG,IAAI,eAAe,CAAgB;QACnD,MAAM,EAAE,KAAK;QACb,MAAM,EAAE,EAAE;KACX,CAAC,CAAC;IAEH,WAAW;IACJ,qBAAqB,GAAG,KAAK,CAAC;IAC9B,qBAAqB,GAAiC,IAAI,CAAC;IAC3D,iBAAiB,GAAG,KAAK,CAAC;IAC1B,aAAa,GAAkB,IAAI,CAAC;IAExB,QAAQ,GAAG,IAAI,OAAO,EAAQ,CAAC;IAClD,IAAY,QAAQ,KAAK,OAAO,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC;IACrD,YAAoB,GAAsB,EAAU,MAAc;QAChE,KAAK,EAAE,CAAC;QADU,QAAG,GAAH,GAAG,CAAmB;QAAU,WAAM,GAAN,MAAM,CAAQ;IAElE,CAAC;IAED,KAAK,CAAC,sBAAsB,CAAC,IAAkB;QAC7C,OAAO,wBAAwB,CAAA;IACjC,CAAC;IAES,aAAa;QACrB,IAAI,CAAC,uBAAuB,EAAE,CAAC;IACjC,CAAC;IAES,QAAQ;QAChB,IAAI,CAAC,eAAe,EAAE,CAAC;IACzB,CAAC;IAEQ,WAAW;QAClB,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QACrB,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC;QACzB,KAAK,CAAC,WAAW,EAAE,CAAC;IACtB,CAAC;IAEM,KAAK,CAAC,eAAe;QAC1B,IAAI,CAAC;YACH,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;YACtB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;YAElB,uCAAuC;YACvC,MAAM,CAAC,IAAI,EAAE,WAAW,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;gBAC5C,IAAI,CAAC,gBAAgB,EAAE;gBACvB,IAAI,CAAC,uBAAuB,EAAE;aAC/B,CAAC,CAAC;YAEH,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;YACzB,IAAI,CAAC,0BAA0B,CAAC,WAAW,CAAC,CAAC;YAC7C,IAAI,CAAC,cAAc,EAAE,CAAC;YACtB,IAAI,CAAC,YAAY,EAAE,CAAC;QAEtB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,iCAAiC,EAAE,KAAK,CAAC,CAAC;YACxD,IAAI,CAAC,KAAK,GAAG,oDAAoD,CAAC;QACpE,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE;gBACnB,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;gBACvB,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;YAC1B,CAAC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,gBAAgB;QAC5B,MAAM,EAAE,GAAG,OAAO,CAAC,oBAAoB,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAC5D,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,OAAO,CAAsB;YACnD,UAAU,EAAE,kBAAkB;YAC9B,UAAU,EAAE,eAAe;YAC3B,OAAO,EAAE,UAAU;SACpB,CAAC,CAAC;QAEH,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;IAC9C,CAAC;IAEO,KAAK,CAAC,uBAAuB;QACnC,MAAM,EAAE,GAAG,OAAO,CAAC,oBAAoB,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAC5D,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,OAAO,CAA4B;YACzD,UAAU,EAAE,0BAA0B;YACtC,UAAU,EAAE,eAAe;YAC3B,OAAO,EAAE,yBAAyB;SACnC,CAAC,CAAC;QAEH,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;IAC9C,CAAC;IAEO,0BAA0B,CAAC,WAAwC;QACzE,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC;QAEzB,KAAK,MAAM,SAAS,IAAI,WAAW,EAAE,CAAC;YACpC,MAAM,KAAK,GAAG,SAAS,CAAC,aAAa,CAAC;YACtC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;gBACjC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YAClC,CAAC;YACD,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC;IAEO,uBAAuB;QAC7B,IAAI,CAAC,QAAQ;aACV,IAAI,CACH,YAAY,CAAC,GAAG,CAAC,EACjB,oBAAoB,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EACvE,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CACzB;aACA,SAAS,CAAC,GAAG,EAAE;YACd,IAAI,CAAC,YAAY,EAAE,CAAC;QACtB,CAAC,CAAC,CAAC;IACP,CAAC;IAEO,YAAY;QAClB,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;QACpC,IAAI,QAAQ,GAAG,CAAC,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC;QAEtC,wEAAwE;QACxE,0EAA0E;QAC1E,IAAI,OAAO,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;YAClC,8CAA8C;YAC9C,QAAQ,GAAG,EAAE,CAAC;QAChB,CAAC;QAED,sBAAsB;QACtB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;YACnB,MAAM,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;YACjD,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAC/B,GAAG,CAAC,IAAI,EAAE,WAAW,EAAE,CAAC,QAAQ,CAAC,WAAW,CAAC;gBAC7C,GAAG,CAAC,WAAW,EAAE,WAAW,EAAE,CAAC,QAAQ,CAAC,WAAW,CAAC,CACrD,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,oBAAoB,GAAG,QAAQ,CAAC;QACrC,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC;IAC3B,CAAC;IAEO,cAAc;QACpB,+CAA+C;QAC/C,MAAM,UAAU,GAAG,IAAI,CAAC,YAAY,CAAC;QACrC,IAAI,aAAa,GAAG,CAAC,CAAC;QACtB,IAAI,cAAc,GAAG,CAAC,CAAC;QAEvB,KAAK,MAAM,CAAC,EAAE,QAAQ,CAAC,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YAC5C,aAAa,IAAI,QAAQ,CAAC,MAAM,CAAC;YACjC,cAAc,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,MAAM,CAAC;QACrE,CAAC;QAED,IAAI,CAAC,KAAK,GAAG;YACX,iBAAiB,EAAE,IAAI,CAAC,YAAY,CAAC,MAAM;YAC3C,kBAAkB,EAAE,UAAU,CAAC,MAAM;YACrC,aAAa;YACb,cAAc;SACf,CAAC;IACJ,CAAC;IAED,8BAA8B;IACvB,oBAAoB,CAAC,MAAqC;QAC/D,IAAI,CAAC,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;IAChC,CAAC;IAEM,YAAY,CAAC,OAA+B;QACjD,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;YACjB,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK;YACtB,GAAG,OAAO;SACX,CAAC,CAAC;QACH,qEAAqE;QACrE,yDAAyD;QACzD,IAAI,CAAC,CAAC,QAAQ,IAAI,OAAO,CAAC,EAAE,CAAC;YAC3B,IAAI,CAAC,YAAY,EAAE,CAAC;YACpB,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;QAC1B,CAAC;IACH,CAAC;IAED,8EAA8E;IAE9E,IAAW,YAAY;QACrB,OAAO;YACL;gBACE,GAAG,EAAE,QAAQ;gBACb,IAAI,EAAE,OAAO;gBACb,KAAK,EAAE,QAAQ;gBACf,WAAW,EAAE;oBACX,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE;oBAC7B,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE;oBACnC,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE;iBACxC;aACF;SACF,CAAC;IACJ,CAAC;IAED,IAAW,YAAY;QACrB,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;IAChD,CAAC;IAED,sEAAsE;IACtE,IAAW,sBAAsB;QAC/B,OAAO,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACtD,CAAC;IAEM,mBAAmB,CAAC,MAA+B;QACxD,IAAI,QAAQ,IAAI,MAAM,EAAE,CAAC;YACvB,IAAI,CAAC,YAAY,CAAC,EAAE,MAAM,EAAG,MAAM,CAAC,QAAQ,CAA6B,IAAI,KAAK,EAAE,CAAC,CAAC;QACxF,CAAC;IACH,CAAC;IAED,mDAAmD;IAC5C,sBAAsB;QAC3B,IAAI,CAAC,YAAY,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;IACvC,CAAC;IAED;sDACkD;IAClD,IAAW,cAAc;QACvB,OAAO,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,KAAK,EAAE,IAAI,IAAI,CAAC,sBAAsB,GAAG,CAAC,CAAC;IAC9E,CAAC;IAED;;qEAEiE;IAC1D,wBAAwB;QAC7B,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC;QAClD,IAAI,CAAC,YAAY,EAAE,CAAC;QACpB,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;IAC1B,CAAC;IAEM,kBAAkB,CAAC,KAAa;QACrC,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,aAAa,KAAK,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC;IACnE,CAAC;IAEM,aAAa,CAAC,KAAa;QAChC,OAAO,IAAI,CAAC,aAAa,KAAK,KAAK,CAAC;IACtC,CAAC;IAEM,cAAc,CAAC,KAAa;QACjC,OAAO,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;IAC3C,CAAC;IAEM,aAAa,CAAC,QAAgB;QACnC,OAAO,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC,CAAC;IACtE,CAAC;IAEM,oBAAoB;QACzB,IAAI,CAAC,qBAAqB,GAAG;YAC3B,IAAI,EAAE,QAAQ;SACf,CAAC;QACF,IAAI,CAAC,qBAAqB,GAAG,IAAI,CAAC;IACpC,CAAC;IAEM,eAAe,CAAC,GAAwB;QAC7C,IAAI,CAAC,qBAAqB,GAAG;YAC3B,WAAW,EAAE,GAAG;YAChB,IAAI,EAAE,MAAM;SACb,CAAC;QACF,IAAI,CAAC,qBAAqB,GAAG,IAAI,CAAC;IACpC,CAAC;IAEM,wBAAwB,CAAC,GAAwB;QACtD,IAAI,CAAC,WAAW,GAAG,GAAG,CAAC;QACvB,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC;IAChC,CAAC;IAEM,KAAK,CAAC,iBAAiB;QAC5B,IAAI,CAAC,IAAI,CAAC,WAAW;YAAE,OAAO;QAE9B,IAAI,CAAC;YACH,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;YACtB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;YAElB,yBAAyB;YACzB,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,CAAC;YACrD,IAAI,CAAC,YAAY,EAAE,CAAC;gBAClB,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,YAAY,EAAE,OAAO,IAAI,8BAA8B,CAAC,CAAC;YAC5F,CAAC;YAED,IAAI,CAAC,iBAAiB,GAAG,KAAK,CAAC;YAC/B,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;YACxB,MAAM,IAAI,CAAC,eAAe,EAAE,CAAC;QAC/B,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,OAAO,CAAC,KAAK,CAAC,6BAA6B,EAAE,KAAK,CAAC,CAAC;YACpD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE;gBACnB,IAAI,CAAC,KAAK,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,8BAA8B,CAAC;gBACrF,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;YAC1B,CAAC,CAAC,CAAC;QACL,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE;gBACnB,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;gBACvB,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;YAC1B,CAAC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAEM,yBAAyB,CAAC,MAA+B;QAC9D,IAAI,CAAC,qBAAqB,GAAG,KAAK,CAAC;QACnC,IAAI,CAAC,qBAAqB,GAAG,IAAI,CAAC;QAElC,IAAI,MAAM,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;YAC7B,0CAA0C;YAC1C,IAAI,CAAC,eAAe,EAAE,CAAC;QACzB,CAAC;IACH,CAAC;IAEM,UAAU,CAAC,GAAwB;QACxC,oEAAoE;QACpE,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;QAE5C,mCAAmC;QACnC,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;YAC1D,OAAO,QAAQ,CAAC;QAClB,CAAC;QACD,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;YACxF,OAAO,eAAe,CAAC;QACzB,CAAC;QACD,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;YAClF,OAAO,UAAU,CAAC;QACpB,CAAC;QACD,IAAI,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;YACzD,OAAO,YAAY,CAAC;QACtB,CAAC;QACD,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;YACvD,OAAO,aAAa,CAAC;QACvB,CAAC;QACD,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;YACvD,OAAO,aAAa,CAAC;QACvB,CAAC;QACD,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;YAChF,OAAO,aAAa,CAAC;QACvB,CAAC;QACD,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;YACzD,OAAO,WAAW,CAAC;QACrB,CAAC;QACD,IAAI,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;YACrF,OAAO,aAAa,CAAC;QACvB,CAAC;QACD,IAAI,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;YACtF,OAAO,eAAe,CAAC;QACzB,CAAC;QACD,IAAI,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;YACrF,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,IAAI,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;YACzF,OAAO,oBAAoB,CAAC;QAC9B,CAAC;QACD,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;YACrF,OAAO,UAAU,CAAC;QACpB,CAAC;QACD,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;YAC/E,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,gCAAgC;QAChC,OAAO,WAAW,CAAC;IACrB,CAAC;IAEM,iBAAiB,CAAC,GAAwB;QAC/C,0CAA0C;QAC1C,OAAO,eAAe,CAAC;IACzB,CAAC;IAEM,iBAAiB,CAAC,GAAwB;QAC/C,0CAA0C;QAC1C,OAAO,QAAQ,CAAC;IAClB,CAAC;IAEM,WAAW;QAChB,IAAI,CAAC,eAAe,EAAE,CAAC;IACzB,CAAC;wHA7XU,8BAA8B;6DAA9B,8BAA8B;YCnBzC,AAPF,kDAGkE,aAItD;YACR,mCAIgB;YAClB,iBAAM;YAKJ,AAJF,8BAAa,wBAOwC;YAAjD,qIAAe,iBAAa,kBAAkB,CAAC,IAAC;YAClD,iBAAiB;YACjB,4CAKiD;YAA/C,8IAAqB,4BAAwB,IAAC;YAC9C,0CAIqC;YAAnC,AADA,wIAAgB,+BAA2B,IAAC,uGACnC,4BAAwB,IAAC;YAGxC,AADE,AADE,iBAAkB,EACA,EAChB;YAEJ,AADF,8BAAa,2BACwD;YAA1B,0HAAW,iBAAa,IAAC;YAAC,iBAAoB;YACvF,iCAA+G;YAAlE,2GAAS,0BAAsB,IAAC;YAC3E,yBAAgC;YAAC,iCAA8B;YAAA,gCAAe;YAGpF,AADE,AADE,AADgF,iBAAO,EAC9E,EACL,EACkB;YAG1B,8CAAuB;YAErB,mGAAiB;YAOjB,mGAA2B;YAc3B,mGAA4B;YAqI5B,iBAAwB;YAGxB,kDAIC;YADC,mIAAU,qCAAiC,IAAC;YAC7C,iBAAwB;YAGzB,oGAAwC;;YA3MpC,eAAqC;YACrC,AADA,uDAAqC,kCACR;YAU7B,eAA+B;YAA/B,iDAA+B;YAM/B,cAAsC;YACtC,AADA,wDAAsC,gDACK;YAGzC,cAAuB;YACvB,AADA,yCAAuB,4BACA;YAOR,eAAqB;YAArB,uCAAqB;YAU1C,eAIC;YAJD,yCAIC;YAGD,cAWC;YAXD,uDAWC;YAGD,cAoIC;YApID,wDAoIC;YAKC,cAAiC;YACjC,AADA,mDAAiC,mCACH;YAKhC,cA4CC;YA5CD,oEA4CC;;;ADtOU,8BAA8B;IAD1C,aAAa,CAAC,aAAa,EAAE,uBAAuB,CAAC;GACzC,8BAA8B,CA8X1C;;iFA9XY,8BAA8B;cAP1C,SAAS;6BACI,KAAK,YACP,2BAA2B;;kFAK1B,8BAA8B","sourcesContent":["import { ChangeDetectorRef, Component, NgZone, OnDestroy } from '@angular/core';\nimport { Subject, BehaviorSubject } from 'rxjs';\nimport { takeUntil, debounceTime, distinctUntilChanged } from 'rxjs/operators';\nimport { RunView, Metadata } from '@memberjunction/core';\nimport { MJApplicationEntity, MJApplicationEntityEntity, ResourceData } from '@memberjunction/core-entities';\nimport { BaseDashboard } from '@memberjunction/ng-shared';\nimport { RegisterClass , UUIDsEqual } from '@memberjunction/global';\nimport { FilterFieldConfig } from '@memberjunction/ng-ui-components';\nimport { ApplicationDialogData, ApplicationDialogResult } from './application-dialog/application-dialog.component';\n\ninterface AppStats {\n totalApplications: number;\n activeApplications: number;\n totalEntities: number;\n publicEntities: number;\n}\n\ninterface FilterOptions {\n status: 'all' | 'active' | 'inactive';\n search: string;\n}\n\n@Component({\n standalone: false,\n selector: 'mj-application-management',\n templateUrl: './application-management.component.html',\n styleUrls: ['../shared/styles/_admin-patterns.css', './application-management.component.css']\n})\n@RegisterClass(BaseDashboard, 'ApplicationManagement')\nexport class ApplicationManagementComponent extends BaseDashboard implements OnDestroy {\n // State management\n public applications: MJApplicationEntity[] = [];\n public filteredApplications: MJApplicationEntity[] = [];\n public selectedApp: MJApplicationEntity | null = null;\n public isLoading = false;\n public error: string | null = null;\n\n // Application entities mapping\n public appEntities: Map<string, MJApplicationEntityEntity[]> = new Map();\n\n // Stats\n public stats: AppStats = {\n totalApplications: 0,\n activeApplications: 0,\n totalEntities: 0,\n publicEntities: 0\n };\n\n // Filters\n public filters$ = new BehaviorSubject<FilterOptions>({\n status: 'all',\n search: ''\n });\n\n // UI State\n public showApplicationDialog = false;\n public applicationDialogData: ApplicationDialogData | null = null;\n public showDeleteConfirm = false;\n public expandedAppId: string | null = null;\n\n protected override destroy$ = new Subject<void>();\n private get metadata() { return this.ProviderToUse; }\n constructor(private cdr: ChangeDetectorRef, private ngZone: NgZone) {\n super();\n }\n\n async GetResourceDisplayName(data: ResourceData): Promise<string> {\n return \"Application Management\"\n }\n\n protected initDashboard(): void {\n this.setupFilterSubscription();\n }\n\n protected loadData(): void {\n this.loadInitialData();\n }\n\n override ngOnDestroy(): void {\n this.destroy$.next();\n this.destroy$.complete();\n super.ngOnDestroy();\n }\n \n public async loadInitialData(): Promise<void> {\n try {\n this.isLoading = true;\n this.error = null;\n \n // Load applications and their entities\n const [apps, appEntities] = await Promise.all([\n this.loadApplications(),\n this.loadApplicationEntities()\n ]);\n \n this.applications = apps;\n this.processApplicationEntities(appEntities);\n this.calculateStats();\n this.applyFilters();\n \n } catch (error) {\n console.error('Error loading application data:', error);\n this.error = 'Failed to load application data. Please try again.';\n } finally {\n this.ngZone.run(() => {\n this.isLoading = false;\n this.cdr.markForCheck();\n });\n }\n }\n\n private async loadApplications(): Promise<MJApplicationEntity[]> {\n const rv = RunView.FromMetadataProvider(this.ProviderToUse);\n const result = await rv.RunView<MJApplicationEntity>({\n EntityName: 'MJ: Applications',\n ResultType: 'entity_object',\n OrderBy: 'Name ASC'\n });\n \n return result.Success ? result.Results : [];\n }\n \n private async loadApplicationEntities(): Promise<MJApplicationEntityEntity[]> {\n const rv = RunView.FromMetadataProvider(this.ProviderToUse);\n const result = await rv.RunView<MJApplicationEntityEntity>({\n EntityName: 'MJ: Application Entities',\n ResultType: 'entity_object',\n OrderBy: 'ApplicationID, Sequence'\n });\n \n return result.Success ? result.Results : [];\n }\n \n private processApplicationEntities(appEntities: MJApplicationEntityEntity[]): void {\n this.appEntities.clear();\n \n for (const appEntity of appEntities) {\n const appId = appEntity.ApplicationID;\n if (!this.appEntities.has(appId)) {\n this.appEntities.set(appId, []);\n }\n this.appEntities.get(appId)!.push(appEntity);\n }\n }\n \n private setupFilterSubscription(): void {\n this.filters$\n .pipe(\n debounceTime(300),\n distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b)),\n takeUntil(this.destroy$)\n )\n .subscribe(() => {\n this.applyFilters();\n });\n }\n \n private applyFilters(): void {\n const filters = this.filters$.value;\n let filtered = [...this.applications];\n \n // Apply status filter - for now, all applications are considered active\n // In the future, we might add an IsActive field to the Applications table\n if (filters.status === 'inactive') {\n // Currently no way to determine inactive apps\n filtered = [];\n }\n \n // Apply search filter\n if (filters.search) {\n const searchLower = filters.search.toLowerCase();\n filtered = filtered.filter(app =>\n app.Name?.toLowerCase().includes(searchLower) ||\n app.Description?.toLowerCase().includes(searchLower)\n );\n }\n \n this.filteredApplications = filtered;\n this.cdr.detectChanges();\n }\n \n private calculateStats(): void {\n // For now, consider all applications as active\n const activeApps = this.applications;\n let totalEntities = 0;\n let publicEntities = 0;\n \n for (const [, entities] of this.appEntities) {\n totalEntities += entities.length;\n publicEntities += entities.filter(e => e.DefaultForNewUser).length;\n }\n \n this.stats = {\n totalApplications: this.applications.length,\n activeApplications: activeApps.length,\n totalEntities,\n publicEntities\n };\n }\n \n // Public methods for template\n public onStatusFilterChange(status: 'all' | 'active' | 'inactive'): void {\n this.updateFilter({ status });\n }\n \n public updateFilter(partial: Partial<FilterOptions>): void {\n this.filters$.next({\n ...this.filters$.value,\n ...partial\n });\n // Discrete changes (chips) apply immediately. Text search still goes\n // through the 300ms debounce in setupFilterSubscription.\n if (!('search' in partial)) {\n this.applyFilters();\n this.cdr.markForCheck();\n }\n }\n\n // -- Concise chrome: one Filter popover (Status) + applied-filter chips -----\n\n public get filterFields(): FilterFieldConfig[] {\n return [\n {\n key: 'status',\n type: 'chips',\n label: 'Status',\n chipOptions: [\n { text: 'All', value: 'all' },\n { text: 'Active', value: 'active' },\n { text: 'Inactive', value: 'inactive' },\n ],\n },\n ];\n }\n\n public get filterValues(): Record<string, unknown> {\n return { status: this.filters$.value.status };\n }\n\n /** Total active filters (Status) — drives the Filter button badge. */\n public get TotalActiveFilterCount(): number {\n return this.filters$.value.status !== 'all' ? 1 : 0;\n }\n\n public onFilterPanelChange(values: Record<string, unknown>): void {\n if ('status' in values) {\n this.updateFilter({ status: (values['status'] as FilterOptions['status']) || 'all' });\n }\n }\n\n /** Clear all filters (Status); search persists. */\n public clearAllAppliedFilters(): void {\n this.updateFilter({ status: 'all' });\n }\n\n /** True when search and/or panel filters are narrowing the list — gates the\n * no-results empty-state \"Reset filters\" CTA. */\n public get IsListNarrowed(): boolean {\n return this.filters$.value.search !== '' || this.TotalActiveFilterCount > 0;\n }\n\n /** Reset everything narrowing the list (search + Status) and refresh\n * immediately. Wired to the no-results empty-state CTA. Unlike\n * clearAllAppliedFilters(), this also clears the search box. */\n public resetAllFiltersAndSearch(): void {\n this.filters$.next({ status: 'all', search: '' });\n this.applyFilters();\n this.cdr.markForCheck();\n }\n \n public toggleAppExpansion(appId: string): void {\n this.expandedAppId = this.expandedAppId === appId ? null : appId;\n }\n \n public isAppExpanded(appId: string): boolean {\n return this.expandedAppId === appId;\n }\n \n public getAppEntities(appId: string): MJApplicationEntityEntity[] {\n return this.appEntities.get(appId) || [];\n }\n \n public getEntityInfo(entityId: string): any {\n return this.metadata.Entities.find(e => UUIDsEqual(e.ID, entityId));\n }\n \n public createNewApplication(): void {\n this.applicationDialogData = {\n mode: 'create'\n };\n this.showApplicationDialog = true;\n }\n \n public editApplication(app: MJApplicationEntity): void {\n this.applicationDialogData = {\n application: app,\n mode: 'edit'\n };\n this.showApplicationDialog = true;\n }\n \n public confirmDeleteApplication(app: MJApplicationEntity): void {\n this.selectedApp = app;\n this.showDeleteConfirm = true;\n }\n \n public async deleteApplication(): Promise<void> {\n if (!this.selectedApp) return;\n \n try {\n this.isLoading = true;\n this.error = null;\n\n // Delete the application\n const deleteResult = await this.selectedApp.Delete();\n if (!deleteResult) {\n throw new Error(this.selectedApp.LatestResult?.Message || 'Failed to delete application');\n }\n\n this.showDeleteConfirm = false;\n this.selectedApp = null;\n await this.loadInitialData();\n } catch (error: unknown) {\n console.error('Error deleting application:', error);\n this.ngZone.run(() => {\n this.error = error instanceof Error ? error.message : 'Failed to delete application';\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 onApplicationDialogResult(result: ApplicationDialogResult): void {\n this.showApplicationDialog = false;\n this.applicationDialogData = null;\n\n if (result.action === 'save') {\n // Refresh the application list after save\n this.loadInitialData();\n }\n }\n \n public getAppIcon(app: MJApplicationEntity): string {\n // Map application names to appropriate icons based on their purpose\n const name = (app.Name || '').toLowerCase();\n\n // Common application type mappings\n if (name.includes('admin') || name.includes('management')) {\n return 'fa-cog';\n }\n if (name.includes('report') || name.includes('analytics') || name.includes('dashboard')) {\n return 'fa-chart-line';\n }\n if (name.includes('user') || name.includes('people') || name.includes('employee')) {\n return 'fa-users';\n }\n if (name.includes('settings') || name.includes('config')) {\n return 'fa-sliders';\n }\n if (name.includes('data') || name.includes('database')) {\n return 'fa-database';\n }\n if (name.includes('file') || name.includes('document')) {\n return 'fa-file-alt';\n }\n if (name.includes('mail') || name.includes('email') || name.includes('message')) {\n return 'fa-envelope';\n }\n if (name.includes('search') || name.includes('explorer')) {\n return 'fa-search';\n }\n if (name.includes('calendar') || name.includes('schedule') || name.includes('event')) {\n return 'fa-calendar';\n }\n if (name.includes('security') || name.includes('auth') || name.includes('permission')) {\n return 'fa-shield-alt';\n }\n if (name.includes('integration') || name.includes('api') || name.includes('connect')) {\n return 'fa-plug';\n }\n if (name.includes('workflow') || name.includes('process') || name.includes('automation')) {\n return 'fa-project-diagram';\n }\n if (name.includes('ai') || name.includes('intelligence') || name.includes('machine')) {\n return 'fa-brain';\n }\n if (name.includes('home') || name.includes('main') || name.includes('default')) {\n return 'fa-home';\n }\n\n // Default icon for applications\n return 'fa-grid-2';\n }\n \n public getAppStatusClass(app: MJApplicationEntity): string {\n // For now, all apps are considered active\n return 'status-active';\n }\n \n public getAppStatusLabel(app: MJApplicationEntity): string {\n // For now, all apps are considered active\n return 'Active';\n }\n \n public refreshData(): void {\n this.loadInitialData();\n }\n}","<!--\n Interior chrome — see Section 10 of explorer-chrome-conventions.\n-->\n<mj-page-header-interior\n AriaLabel=\"Filter applications\"\n Title=\"Applications\"\n Subtitle=\"Configure applications and their entity associations\">\n <!-- X-of-Y filtered count earns its meta spot per chrome conventions §2.\n Partition badges (entities/public) stay dropped — Active/Inactive\n chips in [toolbar] handle the partition signal. -->\n <div meta>\n <mj-stat-badge\n [Count]=\"filteredApplications.length\"\n [Total]=\"applications.length\"\n Label=\"applications\">\n </mj-stat-badge>\n </div>\n <div toolbar>\n <!-- Concise-chrome control bar: search · Filter. Status lives behind the\n one Filter button; applied filters show as removable chips below the\n card. On mobile the popover docks as a bottom sheet. -->\n <mj-page-search\n Placeholder=\"Search applications by name or description...\"\n [Value]=\"filters$.value.search\"\n (ValueChange)=\"updateFilter({ search: $event })\">\n </mj-page-search>\n <mj-filter-popover\n Label=\"Filters\"\n Icon=\"fa-solid fa-filter\"\n [ActiveCount]=\"TotalActiveFilterCount\"\n [ShowClearAll]=\"TotalActiveFilterCount > 0\"\n (ClearAllRequested)=\"clearAllAppliedFilters()\">\n <mj-filter-panel\n [Fields]=\"filterFields\"\n [Values]=\"filterValues\"\n (ValuesChange)=\"onFilterPanelChange($event)\"\n (Reset)=\"clearAllAppliedFilters()\">\n </mj-filter-panel>\n </mj-filter-popover>\n </div>\n <div actions>\n <mj-refresh-button [Loading]=\"isLoading\" (Clicked)=\"refreshData()\"></mj-refresh-button>\n <button mjButton variant=\"primary\" size=\"sm\" (click)=\"createNewApplication()\" aria-label=\"Add new application\">\n <i class=\"fa-solid fa-plus\"></i> <span class=\"mj-action-label\">Add Application</span>\n </button>\n </div>\n</mj-page-header-interior>\n\n<!-- Scrollable Content Section — owned by <mj-page-body-interior>. -->\n<mj-page-body-interior>\n <!-- Loading State -->\n @if (isLoading) {\n <div class=\"loading-container\" role=\"status\" aria-live=\"polite\">\n <mj-loading text=\"Loading applications...\"></mj-loading>\n </div>\n }\n\n <!-- Error State -->\n @if (error && !isLoading) {\n <div class=\"error-container\" role=\"alert\">\n <div class=\"error-content\">\n <i class=\"fa-solid fa-exclamation-triangle error-icon\" aria-hidden=\"true\"></i>\n <p class=\"error-message\">{{ error }}</p>\n <button mjButton variant=\"primary\" (click)=\"loadInitialData()\">\n <i class=\"fa-solid fa-refresh\"></i>\n Try Again\n </button>\n </div>\n </div>\n }\n\n <!-- Applications List -->\n @if (!isLoading && !error) {\n <div class=\"applications-list\" role=\"list\">\n @for (app of filteredApplications; track app.ID) {\n <div class=\"app-card\" [class.expanded]=\"isAppExpanded(app.ID)\" role=\"listitem\">\n <div class=\"app-header\" (click)=\"toggleAppExpansion(app.ID)\" role=\"button\" [attr.aria-expanded]=\"isAppExpanded(app.ID)\">\n <div class=\"app-info\">\n <div class=\"app-icon-wrapper\">\n <i [class]=\"'fa-solid ' + getAppIcon(app)\" aria-hidden=\"true\"></i>\n </div>\n <div class=\"app-details\">\n <h3 class=\"app-name\">{{ app.Name }}</h3>\n <p class=\"app-description\">{{ app.Description || 'No description available' }}</p>\n </div>\n </div>\n\n <div class=\"app-meta\">\n <span class=\"status-badge\" [class]=\"getAppStatusClass(app)\">\n {{ getAppStatusLabel(app) }}\n </span>\n <div class=\"app-actions\" (click)=\"$event.stopPropagation()\">\n <button\n mjButton variant=\"flat\" size=\"sm\"\n (click)=\"editApplication(app)\"\n title=\"Edit\"\n [attr.aria-label]=\"'Edit ' + app.Name\"\n >\n <i class=\"fa-solid fa-edit\"></i>\n </button>\n <button\n mjButton variant=\"danger\" size=\"sm\"\n (click)=\"confirmDeleteApplication(app)\"\n title=\"Delete\"\n [attr.aria-label]=\"'Delete ' + app.Name\"\n >\n <i class=\"fa-solid fa-trash\"></i>\n </button>\n </div>\n <button class=\"expand-btn\" aria-label=\"Toggle details\">\n <i class=\"fa-solid fa-chevron-down\" aria-hidden=\"true\"></i>\n </button>\n </div>\n </div>\n\n @if (isAppExpanded(app.ID)) {\n <div class=\"app-content\">\n <!-- Mobile-only actions bar -->\n <div class=\"mobile-actions-bar\" (click)=\"$event.stopPropagation()\">\n <span class=\"status-badge\" [class]=\"getAppStatusClass(app)\">\n {{ getAppStatusLabel(app) }}\n </span>\n <div class=\"mobile-action-buttons\">\n <button\n mjButton variant=\"flat\" size=\"sm\"\n (click)=\"editApplication(app)\"\n title=\"Edit\"\n [attr.aria-label]=\"'Edit ' + app.Name\"\n >\n <i class=\"fa-solid fa-edit\"></i>\n <span class=\"btn-label\">Edit</span>\n </button>\n <button\n mjButton variant=\"danger\" size=\"sm\"\n (click)=\"confirmDeleteApplication(app)\"\n title=\"Delete\"\n [attr.aria-label]=\"'Delete ' + app.Name\"\n >\n <i class=\"fa-solid fa-trash\"></i>\n <span class=\"btn-label\">Delete</span>\n </button>\n </div>\n </div>\n\n <div class=\"app-stats\">\n <div class=\"stat-item\">\n <i class=\"fa-solid fa-database\" aria-hidden=\"true\"></i>\n <span class=\"stat-label\">Entities:</span>\n <span class=\"stat-value\">{{ getAppEntities(app.ID).length }}</span>\n </div>\n <div class=\"stat-item\">\n <i class=\"fa-solid fa-calendar\" aria-hidden=\"true\"></i>\n <span class=\"stat-label\">Created:</span>\n <span class=\"stat-value\">{{ app.__mj_CreatedAt | date:'short' }}</span>\n </div>\n <div class=\"stat-item\">\n <i class=\"fa-solid fa-clock\" aria-hidden=\"true\"></i>\n <span class=\"stat-label\">Updated:</span>\n <span class=\"stat-value\">{{ app.__mj_UpdatedAt | date:'short' }}</span>\n </div>\n </div>\n\n <div class=\"entities-section\">\n <h4 class=\"section-title\">\n <i class=\"fa-solid fa-database\" aria-hidden=\"true\"></i>\n Application Entities\n </h4>\n\n @if (getAppEntities(app.ID).length > 0) {\n <div class=\"entities-grid\">\n @for (appEntity of getAppEntities(app.ID); track appEntity.ID) {\n @if (getEntityInfo(appEntity.EntityID); as entity) {\n <div class=\"entity-item\">\n <div class=\"entity-name\">{{ entity.Name }}</div>\n <div class=\"entity-meta\">\n @if (appEntity.DefaultForNewUser) {\n <span class=\"entity-badge public\">Public</span>\n }\n <span class=\"entity-sequence\">Order: {{ appEntity.Sequence }}</span>\n </div>\n </div>\n }\n }\n </div>\n } @else {\n <p class=\"no-entities\">No entities configured for this application</p>\n }\n </div>\n </div>\n }\n </div>\n }\n\n @if (filteredApplications.length === 0) {\n <mj-empty-state\n [Variant]=\"IsListNarrowed ? 'no-results' : 'empty'\"\n Icon=\"fa-solid fa-cubes\"\n Title=\"No applications found\"\n Message=\"Try adjusting your filters or create a new application\"\n [ActionText]=\"IsListNarrowed ? 'Reset filters' : ''\"\n ActionIcon=\"fa-solid fa-rotate-left\"\n (Action)=\"resetAllFiltersAndSearch()\" />\n }\n </div>\n }\n </mj-page-body-interior>\n\n <!-- Application Dialog -->\n <mj-application-dialog\n [visible]=\"showApplicationDialog\"\n [data]=\"applicationDialogData\"\n (result)=\"onApplicationDialogResult($event)\"\n ></mj-application-dialog>\n\n <!-- Delete Confirmation Dialog -->\n @if (showDeleteConfirm && selectedApp) {\n <div class=\"modal-backdrop\" (click)=\"showDeleteConfirm = false\" role=\"presentation\">\n <div\n class=\"modal-dialog\"\n (click)=\"$event.stopPropagation()\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"delete-dialog-title\"\n aria-describedby=\"delete-dialog-description\"\n >\n <div class=\"modal-header\">\n <h3 class=\"modal-title\" id=\"delete-dialog-title\">\n <i class=\"fa-solid fa-exclamation-triangle text-danger\" aria-hidden=\"true\"></i>\n Confirm Delete\n </h3>\n <button\n class=\"modal-close\"\n (click)=\"showDeleteConfirm = false\"\n aria-label=\"Close dialog\"\n >\n <i class=\"fa-solid fa-times\" aria-hidden=\"true\"></i>\n </button>\n </div>\n <div class=\"modal-body\" id=\"delete-dialog-description\">\n <p>Are you sure you want to delete the application <strong>{{ selectedApp.Name }}</strong>?</p>\n <mj-alert Variant=\"warning\" Message=\"This will remove all entity associations for this application.\" />\n </div>\n <div class=\"modal-footer\">\n <!-- Primary action (Delete) on LEFT per MD3 design guide -->\n <button mjButton variant=\"danger\" (click)=\"deleteApplication()\" [disabled]=\"isLoading\">\n @if (isLoading) {\n <i class=\"fa-solid fa-spinner fa-spin\" aria-hidden=\"true\"></i>\n Deleting...\n } @else {\n <i class=\"fa-solid fa-trash\" aria-hidden=\"true\"></i>\n Delete Application\n }\n </button>\n <button mjButton (click)=\"showDeleteConfirm = false\">\n Cancel\n </button>\n </div>\n </div>\n </div>\n }\n"]}
|
|
1
|
+
{"version":3,"file":"application-management.component.js","sourceRoot":"","sources":["../../../src/lib/application-management/application-management.component.ts","../../../src/lib/application-management/application-management.component.html"],"names":[],"mappings":"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","sourcesContent":["import { ChangeDetectorRef, Component, NgZone, OnDestroy } from '@angular/core';\nimport { Subject, BehaviorSubject } from 'rxjs';\nimport { takeUntil, debounceTime, distinctUntilChanged } from 'rxjs/operators';\nimport { RunView, Metadata, EntityInfo } from '@memberjunction/core';\nimport { MJApplicationEntity, MJApplicationEntityEntity, ResourceData } from '@memberjunction/core-entities';\nimport { BaseDashboard } from '@memberjunction/ng-shared';\nimport { RegisterClass , UUIDsEqual } from '@memberjunction/global';\nimport { FilterFieldConfig } from '@memberjunction/ng-ui-components';\nimport { ApplicationDialogData, ApplicationDialogResult } from './application-dialog/application-dialog.component';\n\ninterface AppStats {\n totalApplications: number;\n activeApplications: number;\n totalEntities: number;\n publicEntities: number;\n}\n\ninterface FilterOptions {\n status: 'all' | 'active' | 'inactive';\n search: string;\n}\n\n@Component({\n standalone: false,\n selector: 'mj-application-management',\n templateUrl: './application-management.component.html',\n styleUrls: ['../shared/styles/_admin-patterns.css', './application-management.component.css']\n})\n@RegisterClass(BaseDashboard, 'ApplicationManagement')\nexport class ApplicationManagementComponent extends BaseDashboard implements OnDestroy {\n // State management\n public applications: MJApplicationEntity[] = [];\n public filteredApplications: MJApplicationEntity[] = [];\n public selectedApp: MJApplicationEntity | null = null;\n public isLoading = false;\n public error: string | null = null;\n\n // Application entities mapping\n public appEntities: Map<string, MJApplicationEntityEntity[]> = new Map();\n\n // Stats\n public stats: AppStats = {\n totalApplications: 0,\n activeApplications: 0,\n totalEntities: 0,\n publicEntities: 0\n };\n\n // Filters\n public filters$ = new BehaviorSubject<FilterOptions>({\n status: 'all',\n search: ''\n });\n\n // UI State\n public showApplicationDialog = false;\n public applicationDialogData: ApplicationDialogData | null = null;\n public showDeleteConfirm = false;\n public expandedAppId: string | null = null;\n\n protected override destroy$ = new Subject<void>();\n private get metadata() { return this.ProviderToUse; }\n constructor(private cdr: ChangeDetectorRef, private ngZone: NgZone) {\n super();\n }\n\n async GetResourceDisplayName(data: ResourceData): Promise<string> {\n return \"Application Management\"\n }\n\n protected initDashboard(): void {\n this.setupFilterSubscription();\n }\n\n protected loadData(): void {\n this.loadInitialData();\n }\n\n override ngOnDestroy(): void {\n this.destroy$.next();\n this.destroy$.complete();\n super.ngOnDestroy();\n }\n \n public async loadInitialData(): Promise<void> {\n try {\n this.isLoading = true;\n this.error = null;\n \n // Load applications and their entities\n const [apps, appEntities] = await Promise.all([\n this.loadApplications(),\n this.loadApplicationEntities()\n ]);\n \n this.applications = apps;\n this.processApplicationEntities(appEntities);\n this.calculateStats();\n this.applyFilters();\n \n } catch (error) {\n console.error('Error loading application data:', error);\n this.error = 'Failed to load application data. Please try again.';\n } finally {\n this.ngZone.run(() => {\n this.isLoading = false;\n this.cdr.markForCheck();\n });\n }\n }\n\n private async loadApplications(): Promise<MJApplicationEntity[]> {\n const rv = RunView.FromMetadataProvider(this.ProviderToUse);\n const result = await rv.RunView<MJApplicationEntity>({\n EntityName: 'MJ: Applications',\n ResultType: 'entity_object',\n OrderBy: 'Name ASC'\n });\n \n return result.Success ? result.Results : [];\n }\n \n private async loadApplicationEntities(): Promise<MJApplicationEntityEntity[]> {\n const rv = RunView.FromMetadataProvider(this.ProviderToUse);\n const result = await rv.RunView<MJApplicationEntityEntity>({\n EntityName: 'MJ: Application Entities',\n ResultType: 'entity_object',\n OrderBy: 'ApplicationID, Sequence'\n });\n \n return result.Success ? result.Results : [];\n }\n \n private processApplicationEntities(appEntities: MJApplicationEntityEntity[]): void {\n this.appEntities.clear();\n \n for (const appEntity of appEntities) {\n const appId = appEntity.ApplicationID;\n if (!this.appEntities.has(appId)) {\n this.appEntities.set(appId, []);\n }\n this.appEntities.get(appId)!.push(appEntity);\n }\n }\n \n private setupFilterSubscription(): void {\n this.filters$\n .pipe(\n debounceTime(300),\n distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b)),\n takeUntil(this.destroy$)\n )\n .subscribe(() => {\n this.applyFilters();\n });\n }\n \n private applyFilters(): void {\n const filters = this.filters$.value;\n let filtered = [...this.applications];\n \n // Apply status filter - for now, all applications are considered active\n // In the future, we might add an IsActive field to the Applications table\n if (filters.status === 'inactive') {\n // Currently no way to determine inactive apps\n filtered = [];\n }\n \n // Apply search filter\n if (filters.search) {\n const searchLower = filters.search.toLowerCase();\n filtered = filtered.filter(app =>\n app.Name?.toLowerCase().includes(searchLower) ||\n app.Description?.toLowerCase().includes(searchLower)\n );\n }\n \n this.filteredApplications = filtered;\n this.cdr.detectChanges();\n }\n \n private calculateStats(): void {\n // For now, consider all applications as active\n const activeApps = this.applications;\n let totalEntities = 0;\n let publicEntities = 0;\n \n for (const [, entities] of this.appEntities) {\n totalEntities += entities.length;\n publicEntities += entities.filter(e => e.DefaultForNewUser).length;\n }\n \n this.stats = {\n totalApplications: this.applications.length,\n activeApplications: activeApps.length,\n totalEntities,\n publicEntities\n };\n }\n \n // Public methods for template\n public onStatusFilterChange(status: 'all' | 'active' | 'inactive'): void {\n this.updateFilter({ status });\n }\n \n public updateFilter(partial: Partial<FilterOptions>): void {\n this.filters$.next({\n ...this.filters$.value,\n ...partial\n });\n // Discrete changes (chips) apply immediately. Text search still goes\n // through the 300ms debounce in setupFilterSubscription.\n if (!('search' in partial)) {\n this.applyFilters();\n this.cdr.markForCheck();\n }\n }\n\n // -- Concise chrome: one Filter popover (Status) + applied-filter chips -----\n\n public get filterFields(): FilterFieldConfig[] {\n return [\n {\n key: 'status',\n type: 'chips',\n label: 'Status',\n chipOptions: [\n { text: 'All', value: 'all' },\n { text: 'Active', value: 'active' },\n { text: 'Inactive', value: 'inactive' },\n ],\n },\n ];\n }\n\n public get filterValues(): Record<string, unknown> {\n return { status: this.filters$.value.status };\n }\n\n /** Total active filters (Status) — drives the Filter button badge. */\n public get TotalActiveFilterCount(): number {\n return this.filters$.value.status !== 'all' ? 1 : 0;\n }\n\n public onFilterPanelChange(values: Record<string, unknown>): void {\n if ('status' in values) {\n this.updateFilter({ status: (values['status'] as FilterOptions['status']) || 'all' });\n }\n }\n\n /** Clear all filters (Status); search persists. */\n public clearAllAppliedFilters(): void {\n this.updateFilter({ status: 'all' });\n }\n\n /** True when search and/or panel filters are narrowing the list — gates the\n * no-results empty-state \"Reset filters\" CTA. */\n public get IsListNarrowed(): boolean {\n return this.filters$.value.search !== '' || this.TotalActiveFilterCount > 0;\n }\n\n /** Reset everything narrowing the list (search + Status) and refresh\n * immediately. Wired to the no-results empty-state CTA. Unlike\n * clearAllAppliedFilters(), this also clears the search box. */\n public resetAllFiltersAndSearch(): void {\n this.filters$.next({ status: 'all', search: '' });\n this.applyFilters();\n this.cdr.markForCheck();\n }\n \n public toggleAppExpansion(appId: string): void {\n this.expandedAppId = this.expandedAppId === appId ? null : appId;\n }\n \n public isAppExpanded(appId: string): boolean {\n return this.expandedAppId === appId;\n }\n \n public getAppEntities(appId: string): MJApplicationEntityEntity[] {\n return this.appEntities.get(appId) || [];\n }\n \n public getEntityInfo(entityId: string): EntityInfo | undefined {\n return this.metadata.Entities.find(e => UUIDsEqual(e.ID, entityId));\n }\n \n public createNewApplication(): void {\n this.applicationDialogData = {\n mode: 'create'\n };\n this.showApplicationDialog = true;\n }\n \n public editApplication(app: MJApplicationEntity): void {\n this.applicationDialogData = {\n application: app,\n mode: 'edit'\n };\n this.showApplicationDialog = true;\n }\n \n public confirmDeleteApplication(app: MJApplicationEntity): void {\n this.selectedApp = app;\n this.showDeleteConfirm = true;\n }\n \n public async deleteApplication(): Promise<void> {\n if (!this.selectedApp) return;\n \n try {\n this.isLoading = true;\n this.error = null;\n\n // Delete the application\n const deleteResult = await this.selectedApp.Delete();\n if (!deleteResult) {\n throw new Error(this.selectedApp.LatestResult?.Message || 'Failed to delete application');\n }\n\n this.showDeleteConfirm = false;\n this.selectedApp = null;\n await this.loadInitialData();\n } catch (error: unknown) {\n console.error('Error deleting application:', error);\n this.ngZone.run(() => {\n this.error = error instanceof Error ? error.message : 'Failed to delete application';\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 onApplicationDialogResult(result: ApplicationDialogResult): void {\n this.showApplicationDialog = false;\n this.applicationDialogData = null;\n\n if (result.action === 'save') {\n // Refresh the application list after save\n this.loadInitialData();\n }\n }\n \n public getAppIcon(app: MJApplicationEntity): string {\n // Map application names to appropriate icons based on their purpose\n const name = (app.Name || '').toLowerCase();\n\n // Common application type mappings\n if (name.includes('admin') || name.includes('management')) {\n return 'fa-cog';\n }\n if (name.includes('report') || name.includes('analytics') || name.includes('dashboard')) {\n return 'fa-chart-line';\n }\n if (name.includes('user') || name.includes('people') || name.includes('employee')) {\n return 'fa-users';\n }\n if (name.includes('settings') || name.includes('config')) {\n return 'fa-sliders';\n }\n if (name.includes('data') || name.includes('database')) {\n return 'fa-database';\n }\n if (name.includes('file') || name.includes('document')) {\n return 'fa-file-alt';\n }\n if (name.includes('mail') || name.includes('email') || name.includes('message')) {\n return 'fa-envelope';\n }\n if (name.includes('search') || name.includes('explorer')) {\n return 'fa-search';\n }\n if (name.includes('calendar') || name.includes('schedule') || name.includes('event')) {\n return 'fa-calendar';\n }\n if (name.includes('security') || name.includes('auth') || name.includes('permission')) {\n return 'fa-shield-alt';\n }\n if (name.includes('integration') || name.includes('api') || name.includes('connect')) {\n return 'fa-plug';\n }\n if (name.includes('workflow') || name.includes('process') || name.includes('automation')) {\n return 'fa-project-diagram';\n }\n if (name.includes('ai') || name.includes('intelligence') || name.includes('machine')) {\n return 'fa-brain';\n }\n if (name.includes('home') || name.includes('main') || name.includes('default')) {\n return 'fa-home';\n }\n\n // Default icon for applications\n return 'fa-grid-2';\n }\n \n public getAppStatusClass(app: MJApplicationEntity): string {\n // For now, all apps are considered active\n return 'status-active';\n }\n \n public getAppStatusLabel(app: MJApplicationEntity): string {\n // For now, all apps are considered active\n return 'Active';\n }\n \n public refreshData(): void {\n this.loadInitialData();\n }\n}","<!--\n Interior chrome — see Section 10 of explorer-chrome-conventions.\n-->\n<mj-page-header-interior\n AriaLabel=\"Filter applications\"\n Title=\"Applications\"\n Subtitle=\"Configure applications and their entity associations\">\n <!-- X-of-Y filtered count earns its meta spot per chrome conventions §2.\n Partition badges (entities/public) stay dropped — Active/Inactive\n chips in [toolbar] handle the partition signal. -->\n <div meta>\n <mj-stat-badge\n [Count]=\"filteredApplications.length\"\n [Total]=\"applications.length\"\n Label=\"applications\">\n </mj-stat-badge>\n </div>\n <div toolbar>\n <!-- Concise-chrome control bar: search · Filter. Status lives behind the\n one Filter button; applied filters show as removable chips below the\n card. On mobile the popover docks as a bottom sheet. -->\n <mj-page-search\n Placeholder=\"Search applications by name or description...\"\n [Value]=\"filters$.value.search\"\n (ValueChange)=\"updateFilter({ search: $event })\">\n </mj-page-search>\n <mj-filter-popover\n Label=\"Filters\"\n Icon=\"fa-solid fa-filter\"\n [ActiveCount]=\"TotalActiveFilterCount\"\n [ShowClearAll]=\"TotalActiveFilterCount > 0\"\n (ClearAllRequested)=\"clearAllAppliedFilters()\">\n <mj-filter-panel\n [Fields]=\"filterFields\"\n [Values]=\"filterValues\"\n (ValuesChange)=\"onFilterPanelChange($event)\"\n (Reset)=\"clearAllAppliedFilters()\">\n </mj-filter-panel>\n </mj-filter-popover>\n </div>\n <div actions>\n <mj-refresh-button [Loading]=\"isLoading\" (Clicked)=\"refreshData()\"></mj-refresh-button>\n <button mjButton variant=\"primary\" size=\"sm\" (click)=\"createNewApplication()\" aria-label=\"Add new application\">\n <i class=\"fa-solid fa-plus\"></i> <span class=\"mj-action-label\">Add Application</span>\n </button>\n </div>\n</mj-page-header-interior>\n\n<!-- Scrollable Content Section — owned by <mj-page-body-interior>. -->\n<mj-page-body-interior>\n <!-- Loading State -->\n @if (isLoading) {\n <div class=\"loading-container\" role=\"status\" aria-live=\"polite\">\n <mj-loading text=\"Loading applications...\"></mj-loading>\n </div>\n }\n\n <!-- Error State -->\n @if (error && !isLoading) {\n <div class=\"error-container\" role=\"alert\">\n <div class=\"error-content\">\n <i class=\"fa-solid fa-exclamation-triangle error-icon\" aria-hidden=\"true\"></i>\n <p class=\"error-message\">{{ error }}</p>\n <button mjButton variant=\"primary\" (click)=\"loadInitialData()\">\n <i class=\"fa-solid fa-refresh\"></i>\n Try Again\n </button>\n </div>\n </div>\n }\n\n <!-- Applications List -->\n @if (!isLoading && !error) {\n <div class=\"applications-list\" role=\"list\">\n @for (app of filteredApplications; track app.ID) {\n <div class=\"app-card\" [class.expanded]=\"isAppExpanded(app.ID)\" role=\"listitem\">\n <div class=\"app-header\" (click)=\"toggleAppExpansion(app.ID)\" role=\"button\" [attr.aria-expanded]=\"isAppExpanded(app.ID)\">\n <div class=\"app-info\">\n <div class=\"app-icon-wrapper\">\n <i [class]=\"'fa-solid ' + getAppIcon(app)\" aria-hidden=\"true\"></i>\n </div>\n <div class=\"app-details\">\n <h3 class=\"app-name\">{{ app.Name }}</h3>\n <p class=\"app-description\">{{ app.Description || 'No description available' }}</p>\n </div>\n </div>\n\n <div class=\"app-meta\">\n <span class=\"status-badge\" [class]=\"getAppStatusClass(app)\">\n {{ getAppStatusLabel(app) }}\n </span>\n <div class=\"app-actions\" (click)=\"$event.stopPropagation()\">\n <button\n mjButton variant=\"flat\" size=\"sm\"\n (click)=\"editApplication(app)\"\n title=\"Edit\"\n [attr.aria-label]=\"'Edit ' + app.Name\"\n >\n <i class=\"fa-solid fa-edit\"></i>\n </button>\n <button\n mjButton variant=\"danger\" size=\"sm\"\n (click)=\"confirmDeleteApplication(app)\"\n title=\"Delete\"\n [attr.aria-label]=\"'Delete ' + app.Name\"\n >\n <i class=\"fa-solid fa-trash\"></i>\n </button>\n </div>\n <button class=\"expand-btn\" aria-label=\"Toggle details\">\n <i class=\"fa-solid fa-chevron-down\" aria-hidden=\"true\"></i>\n </button>\n </div>\n </div>\n\n @if (isAppExpanded(app.ID)) {\n <div class=\"app-content\">\n <!-- Mobile-only actions bar -->\n <div class=\"mobile-actions-bar\" (click)=\"$event.stopPropagation()\">\n <span class=\"status-badge\" [class]=\"getAppStatusClass(app)\">\n {{ getAppStatusLabel(app) }}\n </span>\n <div class=\"mobile-action-buttons\">\n <button\n mjButton variant=\"flat\" size=\"sm\"\n (click)=\"editApplication(app)\"\n title=\"Edit\"\n [attr.aria-label]=\"'Edit ' + app.Name\"\n >\n <i class=\"fa-solid fa-edit\"></i>\n <span class=\"btn-label\">Edit</span>\n </button>\n <button\n mjButton variant=\"danger\" size=\"sm\"\n (click)=\"confirmDeleteApplication(app)\"\n title=\"Delete\"\n [attr.aria-label]=\"'Delete ' + app.Name\"\n >\n <i class=\"fa-solid fa-trash\"></i>\n <span class=\"btn-label\">Delete</span>\n </button>\n </div>\n </div>\n\n <div class=\"app-stats\">\n <div class=\"stat-item\">\n <i class=\"fa-solid fa-database\" aria-hidden=\"true\"></i>\n <span class=\"stat-label\">Entities:</span>\n <span class=\"stat-value\">{{ getAppEntities(app.ID).length }}</span>\n </div>\n <div class=\"stat-item\">\n <i class=\"fa-solid fa-calendar\" aria-hidden=\"true\"></i>\n <span class=\"stat-label\">Created:</span>\n <span class=\"stat-value\">{{ app.__mj_CreatedAt | date:'short' }}</span>\n </div>\n <div class=\"stat-item\">\n <i class=\"fa-solid fa-clock\" aria-hidden=\"true\"></i>\n <span class=\"stat-label\">Updated:</span>\n <span class=\"stat-value\">{{ app.__mj_UpdatedAt | date:'short' }}</span>\n </div>\n </div>\n\n <div class=\"entities-section\">\n <h4 class=\"section-title\">\n <i class=\"fa-solid fa-database\" aria-hidden=\"true\"></i>\n Application Entities\n </h4>\n\n @if (getAppEntities(app.ID).length > 0) {\n <div class=\"entities-grid\">\n @for (appEntity of getAppEntities(app.ID); track appEntity.ID) {\n @if (getEntityInfo(appEntity.EntityID); as entity) {\n <div class=\"entity-item\">\n <div class=\"entity-name\">{{ entity.DisplayNameOrName }}</div>\n <div class=\"entity-meta\">\n @if (appEntity.DefaultForNewUser) {\n <span class=\"entity-badge public\">Public</span>\n }\n <span class=\"entity-sequence\">Order: {{ appEntity.Sequence }}</span>\n </div>\n </div>\n }\n }\n </div>\n } @else {\n <p class=\"no-entities\">No entities configured for this application</p>\n }\n </div>\n </div>\n }\n </div>\n }\n\n @if (filteredApplications.length === 0) {\n <mj-empty-state\n [Variant]=\"IsListNarrowed ? 'no-results' : 'empty'\"\n Icon=\"fa-solid fa-cubes\"\n Title=\"No applications found\"\n Message=\"Try adjusting your filters or create a new application\"\n [ActionText]=\"IsListNarrowed ? 'Reset filters' : ''\"\n ActionIcon=\"fa-solid fa-rotate-left\"\n (Action)=\"resetAllFiltersAndSearch()\" />\n }\n </div>\n }\n </mj-page-body-interior>\n\n <!-- Application Dialog -->\n <mj-application-dialog\n [visible]=\"showApplicationDialog\"\n [data]=\"applicationDialogData\"\n (result)=\"onApplicationDialogResult($event)\"\n ></mj-application-dialog>\n\n <!-- Delete Confirmation Dialog -->\n @if (showDeleteConfirm && selectedApp) {\n <div class=\"modal-backdrop\" (click)=\"showDeleteConfirm = false\" role=\"presentation\">\n <div\n class=\"modal-dialog\"\n (click)=\"$event.stopPropagation()\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"delete-dialog-title\"\n aria-describedby=\"delete-dialog-description\"\n >\n <div class=\"modal-header\">\n <h3 class=\"modal-title\" id=\"delete-dialog-title\">\n <i class=\"fa-solid fa-exclamation-triangle text-danger\" aria-hidden=\"true\"></i>\n Confirm Delete\n </h3>\n <button\n class=\"modal-close\"\n (click)=\"showDeleteConfirm = false\"\n aria-label=\"Close dialog\"\n >\n <i class=\"fa-solid fa-times\" aria-hidden=\"true\"></i>\n </button>\n </div>\n <div class=\"modal-body\" id=\"delete-dialog-description\">\n <p>Are you sure you want to delete the application <strong>{{ selectedApp.Name }}</strong>?</p>\n <mj-alert Variant=\"warning\" Message=\"This will remove all entity associations for this application.\" />\n </div>\n <div class=\"modal-footer\">\n <!-- Primary action (Delete) on LEFT per MD3 design guide -->\n <button mjButton variant=\"danger\" (click)=\"deleteApplication()\" [disabled]=\"isLoading\">\n @if (isLoading) {\n <i class=\"fa-solid fa-spinner fa-spin\" aria-hidden=\"true\"></i>\n Deleting...\n } @else {\n <i class=\"fa-solid fa-trash\" aria-hidden=\"true\"></i>\n Delete Application\n }\n </button>\n <button mjButton (click)=\"showDeleteConfirm = false\">\n Cancel\n </button>\n </div>\n </div>\n </div>\n }\n"]}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { BaseEntity } from '@memberjunction/core';
|
|
2
|
+
/**
|
|
3
|
+
* One row enrolled in a transaction group, paired with the label a user-facing message should use
|
|
4
|
+
* for it — a user's email, "Add Integration". The caller owns the label because only the caller
|
|
5
|
+
* knows what the row means on its screen.
|
|
6
|
+
*/
|
|
7
|
+
export interface EnrolledRow {
|
|
8
|
+
label: string;
|
|
9
|
+
entity: BaseEntity;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The placeholder messages `GraphQLDataProvider` stamps onto EVERY item of a failed transaction
|
|
13
|
+
* group, refused or not.
|
|
14
|
+
*
|
|
15
|
+
* It registers a `BaseEntityResult` on each entity *before* enrolling the row, and when the group
|
|
16
|
+
* fails its transaction callback writes one of these onto that result — `'Transaction failed'` for
|
|
17
|
+
* a Save, `'Transaction failed to commit'` for a Delete. So a row carrying one of these is a row
|
|
18
|
+
* the server did **not** single out: it was accepted and then abandoned when a sibling was
|
|
19
|
+
* refused. Only a row whose message says something else has a reason worth showing an operator.
|
|
20
|
+
*
|
|
21
|
+
* This is a deliberate coupling to two constants in a sibling package, and it is documented rather
|
|
22
|
+
* than removed because there is no other signal that separates them. Both outcomes end up as
|
|
23
|
+
* `Success: false` on `LatestResult`; `recordServerFailure` appends the server's reason only when
|
|
24
|
+
* the server sent one, and appends nothing otherwise, which leaves the provider's placeholder as
|
|
25
|
+
* the latest result. Keeping the knowledge in one place, pinned by a test, is the containment:
|
|
26
|
+
* if the provider's wording changes, one test fails and one constant moves.
|
|
27
|
+
*/
|
|
28
|
+
export declare const PROVIDER_PLACEHOLDER_MESSAGES: ReadonlySet<string>;
|
|
29
|
+
/**
|
|
30
|
+
* The reasons the SERVER gave for the rows it refused, one line per row that carries one.
|
|
31
|
+
*
|
|
32
|
+
* Issue #4309 fixed the server half of this: `ExecuteTransactionGroup` now reports which rows it
|
|
33
|
+
* refused, and `GraphQLTransactionGroup.recordServerFailure` copies each reason onto that item's
|
|
34
|
+
* `BaseEntity.LatestResult`. This is the last hop — without it the reason reaches the entity and
|
|
35
|
+
* stops there, and every caller falls back to its own generic "rolled back" sentence, which is
|
|
36
|
+
* what #4309's "Verify by" rules out.
|
|
37
|
+
*
|
|
38
|
+
* Returns empty when the server named no row. That is a real outcome, not a failure of this
|
|
39
|
+
* function: a transport error or a rollback the server could not attribute to one item leaves
|
|
40
|
+
* nothing worth showing, and the caller should keep its generic message rather than invent detail.
|
|
41
|
+
*/
|
|
42
|
+
export declare function serverRefusalReasons(rows: EnrolledRow[]): string[];
|
|
43
|
+
//# sourceMappingURL=transaction-group-refusals.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transaction-group-refusals.d.ts","sourceRoot":"","sources":["../../../src/lib/user-management/transaction-group-refusals.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAElD;;;;GAIG;AACH,MAAM,WAAW,WAAW;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,UAAU,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,6BAA6B,EAAE,WAAW,CAAC,MAAM,CAG5D,CAAC;AAEH;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,MAAM,EAAE,CASlE"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The placeholder messages `GraphQLDataProvider` stamps onto EVERY item of a failed transaction
|
|
3
|
+
* group, refused or not.
|
|
4
|
+
*
|
|
5
|
+
* It registers a `BaseEntityResult` on each entity *before* enrolling the row, and when the group
|
|
6
|
+
* fails its transaction callback writes one of these onto that result — `'Transaction failed'` for
|
|
7
|
+
* a Save, `'Transaction failed to commit'` for a Delete. So a row carrying one of these is a row
|
|
8
|
+
* the server did **not** single out: it was accepted and then abandoned when a sibling was
|
|
9
|
+
* refused. Only a row whose message says something else has a reason worth showing an operator.
|
|
10
|
+
*
|
|
11
|
+
* This is a deliberate coupling to two constants in a sibling package, and it is documented rather
|
|
12
|
+
* than removed because there is no other signal that separates them. Both outcomes end up as
|
|
13
|
+
* `Success: false` on `LatestResult`; `recordServerFailure` appends the server's reason only when
|
|
14
|
+
* the server sent one, and appends nothing otherwise, which leaves the provider's placeholder as
|
|
15
|
+
* the latest result. Keeping the knowledge in one place, pinned by a test, is the containment:
|
|
16
|
+
* if the provider's wording changes, one test fails and one constant moves.
|
|
17
|
+
*/
|
|
18
|
+
export const PROVIDER_PLACEHOLDER_MESSAGES = new Set([
|
|
19
|
+
'Transaction failed',
|
|
20
|
+
'Transaction failed to commit',
|
|
21
|
+
]);
|
|
22
|
+
/**
|
|
23
|
+
* The reasons the SERVER gave for the rows it refused, one line per row that carries one.
|
|
24
|
+
*
|
|
25
|
+
* Issue #4309 fixed the server half of this: `ExecuteTransactionGroup` now reports which rows it
|
|
26
|
+
* refused, and `GraphQLTransactionGroup.recordServerFailure` copies each reason onto that item's
|
|
27
|
+
* `BaseEntity.LatestResult`. This is the last hop — without it the reason reaches the entity and
|
|
28
|
+
* stops there, and every caller falls back to its own generic "rolled back" sentence, which is
|
|
29
|
+
* what #4309's "Verify by" rules out.
|
|
30
|
+
*
|
|
31
|
+
* Returns empty when the server named no row. That is a real outcome, not a failure of this
|
|
32
|
+
* function: a transport error or a rollback the server could not attribute to one item leaves
|
|
33
|
+
* nothing worth showing, and the caller should keep its generic message rather than invent detail.
|
|
34
|
+
*/
|
|
35
|
+
export function serverRefusalReasons(rows) {
|
|
36
|
+
const lines = [];
|
|
37
|
+
for (const row of rows) {
|
|
38
|
+
const reason = row.entity.LatestResult?.CompleteMessage?.trim();
|
|
39
|
+
if (reason && !PROVIDER_PLACEHOLDER_MESSAGES.has(reason)) {
|
|
40
|
+
lines.push(`${row.label}: ${reason}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return lines;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=transaction-group-refusals.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transaction-group-refusals.js","sourceRoot":"","sources":["../../../src/lib/user-management/transaction-group-refusals.ts"],"names":[],"mappings":"AAYA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,6BAA6B,GAAwB,IAAI,GAAG,CAAC;IACtE,oBAAoB;IACpB,8BAA8B;CACjC,CAAC,CAAC;AAEH;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAAmB;IACpD,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACrB,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,YAAY,EAAE,eAAe,EAAE,IAAI,EAAE,CAAC;QAChE,IAAI,MAAM,IAAI,CAAC,6BAA6B,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YACvD,KAAK,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,KAAK,KAAK,MAAM,EAAE,CAAC,CAAC;QAC1C,CAAC;IACL,CAAC;IACD,OAAO,KAAK,CAAC;AACjB,CAAC","sourcesContent":["import { BaseEntity } from '@memberjunction/core';\n\n/**\n * One row enrolled in a transaction group, paired with the label a user-facing message should use\n * for it — a user's email, \"Add Integration\". The caller owns the label because only the caller\n * knows what the row means on its screen.\n */\nexport interface EnrolledRow {\n label: string;\n entity: BaseEntity;\n}\n\n/**\n * The placeholder messages `GraphQLDataProvider` stamps onto EVERY item of a failed transaction\n * group, refused or not.\n *\n * It registers a `BaseEntityResult` on each entity *before* enrolling the row, and when the group\n * fails its transaction callback writes one of these onto that result — `'Transaction failed'` for\n * a Save, `'Transaction failed to commit'` for a Delete. So a row carrying one of these is a row\n * the server did **not** single out: it was accepted and then abandoned when a sibling was\n * refused. Only a row whose message says something else has a reason worth showing an operator.\n *\n * This is a deliberate coupling to two constants in a sibling package, and it is documented rather\n * than removed because there is no other signal that separates them. Both outcomes end up as\n * `Success: false` on `LatestResult`; `recordServerFailure` appends the server's reason only when\n * the server sent one, and appends nothing otherwise, which leaves the provider's placeholder as\n * the latest result. Keeping the knowledge in one place, pinned by a test, is the containment:\n * if the provider's wording changes, one test fails and one constant moves.\n */\nexport const PROVIDER_PLACEHOLDER_MESSAGES: ReadonlySet<string> = new Set([\n 'Transaction failed',\n 'Transaction failed to commit',\n]);\n\n/**\n * The reasons the SERVER gave for the rows it refused, one line per row that carries one.\n *\n * Issue #4309 fixed the server half of this: `ExecuteTransactionGroup` now reports which rows it\n * refused, and `GraphQLTransactionGroup.recordServerFailure` copies each reason onto that item's\n * `BaseEntity.LatestResult`. This is the last hop — without it the reason reaches the entity and\n * stops there, and every caller falls back to its own generic \"rolled back\" sentence, which is\n * what #4309's \"Verify by\" rules out.\n *\n * Returns empty when the server named no row. That is a real outcome, not a failure of this\n * function: a transport error or a rollback the server could not attribute to one item leaves\n * nothing worth showing, and the caller should keep its generic message rather than invent detail.\n */\nexport function serverRefusalReasons(rows: EnrolledRow[]): string[] {\n const lines: string[] = [];\n for (const row of rows) {\n const reason = row.entity.LatestResult?.CompleteMessage?.trim();\n if (reason && !PROVIDER_PLACEHOLDER_MESSAGES.has(reason)) {\n lines.push(`${row.label}: ${reason}`);\n }\n }\n return lines;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-dialog.component.d.ts","sourceRoot":"","sources":["../../../../src/lib/user-management/user-dialog/user-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,YAAY,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAG7F,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;;
|
|
1
|
+
{"version":3,"file":"user-dialog.component.d.ts","sourceRoot":"","sources":["../../../../src/lib/user-management/user-dialog/user-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,YAAY,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAG7F,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;;AAErE,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC;IACxB,cAAc,EAAE,YAAY,EAAE,CAAC;CAChC;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;IAC5B,eAAe,cAAqB;IACpC,iBAAiB,EAAE,gBAAgB,EAAE,CAAM;;IAelD,QAAQ,IAAI,IAAI;IAIhB,WAAW,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI;IAmBzC,WAAW,IAAI,IAAI;IAInB,OAAO,CAAC,SAAS;IAejB,WAAW,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAM/B,IAAW,WAAW,IAAI,MAAM,CAE/B;IAED,IAAW,UAAU,IAAI,OAAO,CAE/B;YAEa,YAAY;YAkBZ,qBAAqB;IAqB5B,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,IAAI;IAUhD,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAQ1B,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;YAwDxB,eAAe;IAuE7B,8FAA8F;IAC9F,OAAO,CAAC,YAAY;IAIb,QAAQ,IAAI,IAAI;IAIvB,OAAO,CAAC,oBAAoB;yCArRjB,mBAAmB;2CAAnB,mBAAmB;CA2R/B"}
|