@memberjunction/ng-dashboards 6.2.0-edge.4 → 6.2.0-edge.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -0
- package/dist/AI/components/autotagging/autotagging-pipeline-resource.component.d.ts +1 -1
- package/dist/AI/components/autotagging/autotagging-pipeline-resource.component.d.ts.map +1 -1
- package/dist/AI/components/autotagging/autotagging-pipeline-resource.component.js +15 -7
- package/dist/AI/components/autotagging/autotagging-pipeline-resource.component.js.map +1 -1
- package/dist/AI/components/duplicates/duplicate-detection-resource.component.d.ts +7 -1
- package/dist/AI/components/duplicates/duplicate-detection-resource.component.d.ts.map +1 -1
- package/dist/AI/components/duplicates/duplicate-detection-resource.component.js +209 -57
- package/dist/AI/components/duplicates/duplicate-detection-resource.component.js.map +1 -1
- package/dist/AI/components/duplicates/duplicate-detection-run-scope.d.ts +12 -11
- package/dist/AI/components/duplicates/duplicate-detection-run-scope.d.ts.map +1 -1
- package/dist/AI/components/duplicates/duplicate-detection-run-scope.js +15 -15
- package/dist/AI/components/duplicates/duplicate-detection-run-scope.js.map +1 -1
- package/dist/AI/components/execution-monitoring.component.d.ts.map +1 -1
- package/dist/AI/components/execution-monitoring.component.js +28 -16
- package/dist/AI/components/execution-monitoring.component.js.map +1 -1
- package/dist/AI/components/prompts/model-prompt-priority-matrix.component.d.ts +1 -0
- package/dist/AI/components/prompts/model-prompt-priority-matrix.component.d.ts.map +1 -1
- package/dist/AI/components/prompts/model-prompt-priority-matrix.component.js +30 -6
- package/dist/AI/components/prompts/model-prompt-priority-matrix.component.js.map +1 -1
- package/dist/AI/components/tags/tags-resource.component.d.ts +1 -1
- package/dist/AI/components/tags/tags-resource.component.d.ts.map +1 -1
- package/dist/AI/components/tags/tags-resource.component.js +15 -7
- package/dist/AI/components/tags/tags-resource.component.js.map +1 -1
- package/dist/AI/components/vectors/sensitive-fields.d.ts +58 -0
- package/dist/AI/components/vectors/sensitive-fields.d.ts.map +1 -0
- package/dist/AI/components/vectors/sensitive-fields.js +251 -0
- package/dist/AI/components/vectors/sensitive-fields.js.map +1 -0
- package/dist/AI/components/vectors/vector-document-rules.d.ts +139 -0
- package/dist/AI/components/vectors/vector-document-rules.d.ts.map +1 -0
- package/dist/AI/components/vectors/vector-document-rules.js +182 -0
- package/dist/AI/components/vectors/vector-document-rules.js.map +1 -0
- package/dist/AI/components/vectors/vector-management-resource.component.d.ts +83 -14
- package/dist/AI/components/vectors/vector-management-resource.component.d.ts.map +1 -1
- package/dist/AI/components/vectors/vector-management-resource.component.js +546 -271
- package/dist/AI/components/vectors/vector-management-resource.component.js.map +1 -1
- package/dist/AI/components/vectors/vector-store-selection.d.ts +32 -0
- package/dist/AI/components/vectors/vector-store-selection.d.ts.map +1 -0
- package/dist/AI/components/vectors/vector-store-selection.js +57 -0
- package/dist/AI/components/vectors/vector-store-selection.js.map +1 -0
- package/dist/ChiefOfStaff/chief-of-staff-agent-context.d.ts +236 -0
- package/dist/ChiefOfStaff/chief-of-staff-agent-context.d.ts.map +1 -0
- package/dist/ChiefOfStaff/chief-of-staff-agent-context.js +374 -0
- package/dist/ChiefOfStaff/chief-of-staff-agent-context.js.map +1 -0
- package/dist/ChiefOfStaff/chief-of-staff-dashboard.component.d.ts +195 -0
- package/dist/ChiefOfStaff/chief-of-staff-dashboard.component.d.ts.map +1 -0
- package/dist/ChiefOfStaff/chief-of-staff-dashboard.component.js +2910 -0
- package/dist/ChiefOfStaff/chief-of-staff-dashboard.component.js.map +1 -0
- package/dist/ChiefOfStaff/index.d.ts +3 -0
- package/dist/ChiefOfStaff/index.d.ts.map +1 -0
- package/dist/ChiefOfStaff/index.js +3 -0
- package/dist/ChiefOfStaff/index.js.map +1 -0
- package/dist/Communication/communication-agent-context.d.ts +11 -1
- package/dist/Communication/communication-agent-context.d.ts.map +1 -1
- package/dist/Communication/communication-agent-context.js +15 -0
- package/dist/Communication/communication-agent-context.js.map +1 -1
- package/dist/Communication/communication-dashboard.component.d.ts.map +1 -1
- package/dist/Communication/communication-dashboard.component.js +5 -0
- package/dist/Communication/communication-dashboard.component.js.map +1 -1
- package/dist/Communication/communication-logs-resource.component.d.ts.map +1 -1
- package/dist/Communication/communication-logs-resource.component.js +2 -1
- package/dist/Communication/communication-logs-resource.component.js.map +1 -1
- package/dist/Communication/communication-monitor-resource.component.d.ts.map +1 -1
- package/dist/Communication/communication-monitor-resource.component.js +23 -18
- package/dist/Communication/communication-monitor-resource.component.js.map +1 -1
- package/dist/Communication/communication-providers-resource.component.d.ts +27 -1
- package/dist/Communication/communication-providers-resource.component.d.ts.map +1 -1
- package/dist/Communication/communication-providers-resource.component.js +722 -78
- package/dist/Communication/communication-providers-resource.component.js.map +1 -1
- package/dist/Communication/communication-runs-resource.component.d.ts.map +1 -1
- package/dist/Communication/communication-runs-resource.component.js +11 -10
- package/dist/Communication/communication-runs-resource.component.js.map +1 -1
- package/dist/DashboardEditor/dashboard-editor.component.d.ts +17 -2
- package/dist/DashboardEditor/dashboard-editor.component.d.ts.map +1 -1
- package/dist/DashboardEditor/dashboard-editor.component.js +94 -43
- package/dist/DashboardEditor/dashboard-editor.component.js.map +1 -1
- package/dist/DataExplorer/data-explorer-dashboard.component.d.ts +49 -0
- package/dist/DataExplorer/data-explorer-dashboard.component.d.ts.map +1 -1
- package/dist/DataExplorer/data-explorer-dashboard.component.js +220 -55
- package/dist/DataExplorer/data-explorer-dashboard.component.js.map +1 -1
- package/dist/DataExplorer/models/explorer-state.interface.d.ts +2 -0
- package/dist/DataExplorer/models/explorer-state.interface.d.ts.map +1 -1
- package/dist/DataExplorer/models/explorer-state.interface.js.map +1 -1
- package/dist/DevTools/app-state-inspector.component.d.ts.map +1 -1
- package/dist/DevTools/app-state-inspector.component.js +1 -0
- package/dist/DevTools/app-state-inspector.component.js.map +1 -1
- package/dist/DevTools/class-registry.component.d.ts.map +1 -1
- package/dist/DevTools/class-registry.component.js +1 -0
- package/dist/DevTools/class-registry.component.js.map +1 -1
- package/dist/DevTools/event-monitor.component.d.ts.map +1 -1
- package/dist/DevTools/event-monitor.component.js +2 -0
- package/dist/DevTools/event-monitor.component.js.map +1 -1
- package/dist/DevTools/graphql-console.component.d.ts.map +1 -1
- package/dist/DevTools/graphql-console.component.js.map +1 -1
- package/dist/DevTools/grid-width-lab.component.d.ts.map +1 -1
- package/dist/DevTools/grid-width-lab.component.js +1 -0
- package/dist/DevTools/grid-width-lab.component.js.map +1 -1
- package/dist/DevTools/layout-inspector.component.d.ts.map +1 -1
- package/dist/DevTools/layout-inspector.component.js +1 -0
- package/dist/DevTools/layout-inspector.component.js.map +1 -1
- package/dist/DevTools/lazy-module-status.component.d.ts.map +1 -1
- package/dist/DevTools/lazy-module-status.component.js +1 -0
- package/dist/DevTools/lazy-module-status.component.js.map +1 -1
- package/dist/DevTools/settings-explorer.component.d.ts.map +1 -1
- package/dist/DevTools/settings-explorer.component.js +1 -0
- package/dist/DevTools/settings-explorer.component.js.map +1 -1
- package/dist/DevTools/tab-strip-lab.component.d.ts.map +1 -1
- package/dist/DevTools/tab-strip-lab.component.js +1 -0
- package/dist/DevTools/tab-strip-lab.component.js.map +1 -1
- package/dist/EntityAdmin/entity-admin-dashboard.component.d.ts +5 -0
- package/dist/EntityAdmin/entity-admin-dashboard.component.d.ts.map +1 -1
- package/dist/EntityAdmin/entity-admin-dashboard.component.js +8 -5
- package/dist/EntityAdmin/entity-admin-dashboard.component.js.map +1 -1
- package/dist/Integration/components/overview/overview.component.js +2 -2
- package/dist/KnowledgeHub/components/config/knowledge-config-resource.component.js +2 -2
- package/dist/KnowledgeHub/components/config/knowledge-config-resource.component.js.map +1 -1
- package/dist/KnowledgeHub/components/feature-pipelines/feature-pipeline.engine.d.ts +11 -1
- package/dist/KnowledgeHub/components/feature-pipelines/feature-pipeline.engine.d.ts.map +1 -1
- package/dist/KnowledgeHub/components/feature-pipelines/feature-pipeline.engine.js +24 -2
- package/dist/KnowledgeHub/components/feature-pipelines/feature-pipeline.engine.js.map +1 -1
- package/dist/KnowledgeHub/components/feature-pipelines/feature-pipelines-resource.component.d.ts +16 -2
- package/dist/KnowledgeHub/components/feature-pipelines/feature-pipelines-resource.component.d.ts.map +1 -1
- package/dist/KnowledgeHub/components/feature-pipelines/feature-pipelines-resource.component.js +87 -33
- package/dist/KnowledgeHub/components/feature-pipelines/feature-pipelines-resource.component.js.map +1 -1
- package/dist/LoginStudio/login-studio-inspector.component.d.ts +130 -0
- package/dist/LoginStudio/login-studio-inspector.component.d.ts.map +1 -0
- package/dist/LoginStudio/login-studio-inspector.component.js +781 -0
- package/dist/LoginStudio/login-studio-inspector.component.js.map +1 -0
- package/dist/LoginStudio/login-studio-resource.component.d.ts +161 -0
- package/dist/LoginStudio/login-studio-resource.component.d.ts.map +1 -0
- package/dist/LoginStudio/login-studio-resource.component.js +890 -0
- package/dist/LoginStudio/login-studio-resource.component.js.map +1 -0
- package/dist/MCP/mcp-dashboard.component.d.ts +24 -4
- package/dist/MCP/mcp-dashboard.component.d.ts.map +1 -1
- package/dist/MCP/mcp-dashboard.component.js +118 -65
- package/dist/MCP/mcp-dashboard.component.js.map +1 -1
- package/dist/PredictiveStudio/business-predictions.view-models.d.ts +1 -1
- package/dist/PredictiveStudio/business-predictions.view-models.d.ts.map +1 -1
- package/dist/PredictiveStudio/business-predictions.view-models.js +3 -3
- package/dist/PredictiveStudio/business-predictions.view-models.js.map +1 -1
- package/dist/PredictiveStudio/components/ps-experiments.component.d.ts +6 -63
- package/dist/PredictiveStudio/components/ps-experiments.component.d.ts.map +1 -1
- package/dist/PredictiveStudio/components/ps-experiments.component.js +256 -521
- package/dist/PredictiveStudio/components/ps-experiments.component.js.map +1 -1
- package/dist/PredictiveStudio/components/ps-pipeline-wizard.component.d.ts +0 -18
- package/dist/PredictiveStudio/components/ps-pipeline-wizard.component.d.ts.map +1 -1
- package/dist/PredictiveStudio/components/ps-pipeline-wizard.component.js +122 -249
- package/dist/PredictiveStudio/components/ps-pipeline-wizard.component.js.map +1 -1
- package/dist/PredictiveStudio/components/ps-pipelines.component.d.ts +5 -9
- package/dist/PredictiveStudio/components/ps-pipelines.component.d.ts.map +1 -1
- package/dist/PredictiveStudio/components/ps-pipelines.component.js +98 -130
- package/dist/PredictiveStudio/components/ps-pipelines.component.js.map +1 -1
- package/dist/PredictiveStudio/components/ps-predictions-grid.component.d.ts +53 -4
- package/dist/PredictiveStudio/components/ps-predictions-grid.component.d.ts.map +1 -1
- package/dist/PredictiveStudio/components/ps-predictions-grid.component.js +335 -326
- package/dist/PredictiveStudio/components/ps-predictions-grid.component.js.map +1 -1
- package/dist/PredictiveStudio/components/ps-row-drivers.component.d.ts +19 -0
- package/dist/PredictiveStudio/components/ps-row-drivers.component.d.ts.map +1 -0
- package/dist/PredictiveStudio/components/ps-row-drivers.component.js +85 -0
- package/dist/PredictiveStudio/components/ps-row-drivers.component.js.map +1 -0
- package/dist/PredictiveStudio/engine/predictive-studio.engine.d.ts +19 -1
- package/dist/PredictiveStudio/engine/predictive-studio.engine.d.ts.map +1 -1
- package/dist/PredictiveStudio/engine/predictive-studio.engine.js +51 -2
- package/dist/PredictiveStudio/engine/predictive-studio.engine.js.map +1 -1
- package/dist/PredictiveStudio/predictive-studio-agent-context.d.ts +3 -1
- package/dist/PredictiveStudio/predictive-studio-agent-context.d.ts.map +1 -1
- package/dist/PredictiveStudio/predictive-studio-agent-context.js +3 -0
- package/dist/PredictiveStudio/predictive-studio-agent-context.js.map +1 -1
- package/dist/PredictiveStudio/predictive-studio-score-history.service.d.ts +27 -4
- package/dist/PredictiveStudio/predictive-studio-score-history.service.d.ts.map +1 -1
- package/dist/PredictiveStudio/predictive-studio-score-history.service.js +94 -61
- package/dist/PredictiveStudio/predictive-studio-score-history.service.js.map +1 -1
- package/dist/PredictiveStudio/resources/ps-predictions-resource.component.d.ts +29 -5
- package/dist/PredictiveStudio/resources/ps-predictions-resource.component.d.ts.map +1 -1
- package/dist/PredictiveStudio/resources/ps-predictions-resource.component.js +185 -103
- package/dist/PredictiveStudio/resources/ps-predictions-resource.component.js.map +1 -1
- package/dist/PredictiveStudio/resources/ps-studio-resource.component.js +1 -1
- package/dist/PredictiveStudio/resources/ps-studio-resource.component.js.map +1 -1
- package/dist/Workflows/components/workflow-runs-resource.component.js +4 -4
- package/dist/Workflows/components/workflow-runs-resource.component.js.map +1 -1
- package/dist/ai-dashboards.module.d.ts +22 -21
- package/dist/ai-dashboards.module.d.ts.map +1 -1
- package/dist/ai-dashboards.module.js +8 -2
- package/dist/ai-dashboards.module.js.map +1 -1
- package/dist/core-dashboards.module.d.ts +2 -1
- package/dist/core-dashboards.module.d.ts.map +1 -1
- package/dist/core-dashboards.module.js +11 -3
- package/dist/core-dashboards.module.js.map +1 -1
- package/dist/public-api.d.ts +3 -0
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +4 -0
- package/dist/public-api.js.map +1 -1
- package/package.json +72 -69
- package/dist/PredictiveStudio/at-risk.view-models.d.ts +0 -120
- package/dist/PredictiveStudio/at-risk.view-models.d.ts.map +0 -1
- package/dist/PredictiveStudio/at-risk.view-models.js +0 -261
- package/dist/PredictiveStudio/at-risk.view-models.js.map +0 -1
- package/dist/PredictiveStudio/components/ps-confirm-modal.component.d.ts +0 -109
- package/dist/PredictiveStudio/components/ps-confirm-modal.component.d.ts.map +0 -1
- package/dist/PredictiveStudio/components/ps-confirm-modal.component.js +0 -323
- package/dist/PredictiveStudio/components/ps-confirm-modal.component.js.map +0 -1
- package/dist/PredictiveStudio/components/ps-run-experiment-modal.component.d.ts +0 -137
- package/dist/PredictiveStudio/components/ps-run-experiment-modal.component.d.ts.map +0 -1
- package/dist/PredictiveStudio/components/ps-run-experiment-modal.component.js +0 -826
- package/dist/PredictiveStudio/components/ps-run-experiment-modal.component.js.map +0 -1
|
@@ -9,7 +9,7 @@ import { Component, Input, Output, EventEmitter, HostListener, ViewChild, inject
|
|
|
9
9
|
import { trigger, transition, style, animate } from '@angular/animations';
|
|
10
10
|
import { Subject } from 'rxjs';
|
|
11
11
|
import { takeUntil, debounceTime } from 'rxjs/operators';
|
|
12
|
-
import { BaseDashboard } from '@memberjunction/ng-shared';
|
|
12
|
+
import { BaseDashboard, GetDeletedRecordFromEvent, RecordIdentityMatches, NormalizeRecordIdForMatch } from '@memberjunction/ng-shared';
|
|
13
13
|
import { ApplicationManager } from '@memberjunction/ng-base-application';
|
|
14
14
|
import { MJNotificationService } from '@memberjunction/ng-notifications';
|
|
15
15
|
import { IndexByUUID, RegisterClass, UUIDMap, UUIDsEqual } from '@memberjunction/global';
|
|
@@ -612,11 +612,11 @@ function DataExplorerDashboardComponent_Conditional_15_Template(rf, ctx) { if (r
|
|
|
612
612
|
function DataExplorerDashboardComponent_Conditional_16_Template(rf, ctx) { if (rf & 1) {
|
|
613
613
|
const _r27 = i0.ɵɵgetCurrentView();
|
|
614
614
|
i0.ɵɵelementStart(0, "mj-view-workspace", 91);
|
|
615
|
-
i0.ɵɵlistener("SelectedViewChange", function DataExplorerDashboardComponent_Conditional_16_Template_mj_view_workspace_SelectedViewChange_0_listener($event) { i0.ɵɵrestoreView(_r27); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onWorkspaceViewSelected($event)); })("ViewSelected", function DataExplorerDashboardComponent_Conditional_16_Template_mj_view_workspace_ViewSelected_0_listener($event) { i0.ɵɵrestoreView(_r27); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onWorkspaceViewSelected($event)); })("OpenViewInTabRequested", function DataExplorerDashboardComponent_Conditional_16_Template_mj_view_workspace_OpenViewInTabRequested_0_listener($event) { i0.ɵɵrestoreView(_r27); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onOpenInTabRequested($event)); })("OpenRecordRequested", function DataExplorerDashboardComponent_Conditional_16_Template_mj_view_workspace_OpenRecordRequested_0_listener($event) { i0.ɵɵrestoreView(_r27); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onWorkspaceOpenRecord($event)); })("OpenRelatedRecordRequested", function DataExplorerDashboardComponent_Conditional_16_Template_mj_view_workspace_OpenRelatedRecordRequested_0_listener($event) { i0.ɵɵrestoreView(_r27); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onOpenRelatedRecordRequested($event)); })("CreateNewRecordRequested", function DataExplorerDashboardComponent_Conditional_16_Template_mj_view_workspace_CreateNewRecordRequested_0_listener($event) { i0.ɵɵrestoreView(_r27); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onCreateNewRecordRequested($event)); })("RecordSelected", function DataExplorerDashboardComponent_Conditional_16_Template_mj_view_workspace_RecordSelected_0_listener($event) { i0.ɵɵrestoreView(_r27); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onViewerRecordSelected($event)); })("AfterViewSave", function DataExplorerDashboardComponent_Conditional_16_Template_mj_view_workspace_AfterViewSave_0_listener() { i0.ɵɵrestoreView(_r27); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onWorkspaceViewSaved()); })("AfterViewDelete", function DataExplorerDashboardComponent_Conditional_16_Template_mj_view_workspace_AfterViewDelete_0_listener() { i0.ɵɵrestoreView(_r27); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onWorkspaceViewSaved()); })("FilterTextChanged", function DataExplorerDashboardComponent_Conditional_16_Template_mj_view_workspace_FilterTextChanged_0_listener($event) { i0.ɵɵrestoreView(_r27); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onFilterTextChanged($event)); })("DataLoaded", function DataExplorerDashboardComponent_Conditional_16_Template_mj_view_workspace_DataLoaded_0_listener($event) { i0.ɵɵrestoreView(_r27); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onDataLoaded($event)); })("FilteredCountChanged", function DataExplorerDashboardComponent_Conditional_16_Template_mj_view_workspace_FilteredCountChanged_0_listener($event) { i0.ɵɵrestoreView(_r27); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onFilteredCountChanged($event)); });
|
|
615
|
+
i0.ɵɵlistener("SelectedViewChange", function DataExplorerDashboardComponent_Conditional_16_Template_mj_view_workspace_SelectedViewChange_0_listener($event) { i0.ɵɵrestoreView(_r27); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onWorkspaceViewSelected($event)); })("ViewSelected", function DataExplorerDashboardComponent_Conditional_16_Template_mj_view_workspace_ViewSelected_0_listener($event) { i0.ɵɵrestoreView(_r27); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onWorkspaceViewSelected($event)); })("AlternateBaseViewChange", function DataExplorerDashboardComponent_Conditional_16_Template_mj_view_workspace_AlternateBaseViewChange_0_listener($event) { i0.ɵɵrestoreView(_r27); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnWorkspaceAlternateBaseViewChanged($event)); })("OpenViewInTabRequested", function DataExplorerDashboardComponent_Conditional_16_Template_mj_view_workspace_OpenViewInTabRequested_0_listener($event) { i0.ɵɵrestoreView(_r27); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onOpenInTabRequested($event)); })("OpenRecordRequested", function DataExplorerDashboardComponent_Conditional_16_Template_mj_view_workspace_OpenRecordRequested_0_listener($event) { i0.ɵɵrestoreView(_r27); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onWorkspaceOpenRecord($event)); })("OpenRelatedRecordRequested", function DataExplorerDashboardComponent_Conditional_16_Template_mj_view_workspace_OpenRelatedRecordRequested_0_listener($event) { i0.ɵɵrestoreView(_r27); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onOpenRelatedRecordRequested($event)); })("CreateNewRecordRequested", function DataExplorerDashboardComponent_Conditional_16_Template_mj_view_workspace_CreateNewRecordRequested_0_listener($event) { i0.ɵɵrestoreView(_r27); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onCreateNewRecordRequested($event)); })("RecordSelected", function DataExplorerDashboardComponent_Conditional_16_Template_mj_view_workspace_RecordSelected_0_listener($event) { i0.ɵɵrestoreView(_r27); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onViewerRecordSelected($event)); })("AfterViewSave", function DataExplorerDashboardComponent_Conditional_16_Template_mj_view_workspace_AfterViewSave_0_listener() { i0.ɵɵrestoreView(_r27); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onWorkspaceViewSaved()); })("AfterViewDelete", function DataExplorerDashboardComponent_Conditional_16_Template_mj_view_workspace_AfterViewDelete_0_listener() { i0.ɵɵrestoreView(_r27); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onWorkspaceViewSaved()); })("FilterTextChanged", function DataExplorerDashboardComponent_Conditional_16_Template_mj_view_workspace_FilterTextChanged_0_listener($event) { i0.ɵɵrestoreView(_r27); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onFilterTextChanged($event)); })("DataLoaded", function DataExplorerDashboardComponent_Conditional_16_Template_mj_view_workspace_DataLoaded_0_listener($event) { i0.ɵɵrestoreView(_r27); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onDataLoaded($event)); })("FilteredCountChanged", function DataExplorerDashboardComponent_Conditional_16_Template_mj_view_workspace_FilteredCountChanged_0_listener($event) { i0.ɵɵrestoreView(_r27); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onFilteredCountChanged($event)); });
|
|
616
616
|
i0.ɵɵelementEnd();
|
|
617
617
|
} if (rf & 2) {
|
|
618
618
|
const ctx_r1 = i0.ɵɵnextContext();
|
|
619
|
-
i0.ɵɵproperty("Entity", ctx_r1.selectedEntity)("AutoSaveView", true)("SelectedView", ctx_r1.selectedViewEntity)("FilterText", ctx_r1.debouncedFilterText)("SelectedRecordId", ctx_r1.state.selectedRecordId)("ViewerConfig", ctx_r1.viewerConfig);
|
|
619
|
+
i0.ɵɵproperty("Entity", ctx_r1.selectedEntity)("AutoSaveView", true)("SelectedView", ctx_r1.selectedViewEntity)("AlternateBaseView", ctx_r1.SelectedAlternateBaseView)("FilterText", ctx_r1.debouncedFilterText)("SelectedRecordId", ctx_r1.state.selectedRecordId)("ViewerConfig", ctx_r1.viewerConfig);
|
|
620
620
|
} }
|
|
621
621
|
function DataExplorerDashboardComponent_Conditional_17_Template(rf, ctx) { if (rf & 1) {
|
|
622
622
|
const _r28 = i0.ɵɵgetCurrentView();
|
|
@@ -832,6 +832,11 @@ let DataExplorerDashboardComponent = class DataExplorerDashboardComponent extend
|
|
|
832
832
|
loadedRecords = [];
|
|
833
833
|
// Currently selected view entity (for view data loading)
|
|
834
834
|
SelectedViewEntity = null;
|
|
835
|
+
/**
|
|
836
|
+
* Optional semantic name of an alternate base view selected via deep link (`?abv=<Name>`)
|
|
837
|
+
* when no saved view is active.
|
|
838
|
+
*/
|
|
839
|
+
SelectedAlternateBaseView = null;
|
|
835
840
|
/** @deprecated Use {@link SelectedViewEntity}. */
|
|
836
841
|
get selectedViewEntity() {
|
|
837
842
|
return this.SelectedViewEntity;
|
|
@@ -950,6 +955,8 @@ let DataExplorerDashboardComponent = class DataExplorerDashboardComponent extend
|
|
|
950
955
|
// ========================================
|
|
951
956
|
// Concept D: Application Groups + Search-First
|
|
952
957
|
// ========================================
|
|
958
|
+
_filteredAppEntityGroupsCache = [];
|
|
959
|
+
_filteredAppEntityGroupsKey = '';
|
|
953
960
|
/**
|
|
954
961
|
* Get app entity groups filtered by current entityFilterText and homeViewMode.
|
|
955
962
|
* When searching, auto-expands groups that contain matches.
|
|
@@ -957,9 +964,16 @@ let DataExplorerDashboardComponent = class DataExplorerDashboardComponent extend
|
|
|
957
964
|
get FilteredAppEntityGroups() {
|
|
958
965
|
const filterText = this.EntityFilterText.toLowerCase().trim();
|
|
959
966
|
const showFavoritesOnly = this.State.homeViewMode === 'favorites';
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
967
|
+
const expandedSig = this.AppEntityGroups.map(g => `${g.applicationId}:${g.isExpanded ? 1 : 0}:${g.entities.length}`).join('|');
|
|
968
|
+
const favSig = showFavoritesOnly ? this.State.favoriteEntities.map(f => f.entityId).join(',') : '';
|
|
969
|
+
const key = `${filterText}::${showFavoritesOnly ? 1 : 0}::${expandedSig}::${favSig}`;
|
|
970
|
+
if (key !== this._filteredAppEntityGroupsKey) {
|
|
971
|
+
this._filteredAppEntityGroupsKey = key;
|
|
972
|
+
this._filteredAppEntityGroupsCache = this.AppEntityGroups
|
|
973
|
+
.map(group => this.filterGroupEntities(group, filterText, showFavoritesOnly))
|
|
974
|
+
.filter(group => group.entities.length > 0);
|
|
975
|
+
}
|
|
976
|
+
return this._filteredAppEntityGroupsCache;
|
|
963
977
|
}
|
|
964
978
|
/** @deprecated Use {@link FilteredAppEntityGroups}. */
|
|
965
979
|
get filteredAppEntityGroups() {
|
|
@@ -985,22 +999,33 @@ let DataExplorerDashboardComponent = class DataExplorerDashboardComponent extend
|
|
|
985
999
|
isExpanded: filterText ? true : group.isExpanded
|
|
986
1000
|
};
|
|
987
1001
|
}
|
|
1002
|
+
_flatFilteredEntitiesCache = [];
|
|
1003
|
+
_flatFilteredEntitiesSource = null;
|
|
1004
|
+
_flatFilteredEntitiesKey = '';
|
|
988
1005
|
/**
|
|
989
1006
|
* Get a flat filtered entity list for single-application mode.
|
|
990
1007
|
* Used when entityFilter.applicationId is set, bypassing app grouping.
|
|
991
1008
|
*/
|
|
992
1009
|
get FlatFilteredEntities() {
|
|
993
|
-
let result = this.Entities;
|
|
994
|
-
if (this.State.homeViewMode === 'favorites') {
|
|
995
|
-
result = result.filter(e => this.IsEntityFavorited(e));
|
|
996
|
-
}
|
|
997
1010
|
const filterText = this.EntityFilterText.toLowerCase().trim();
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1011
|
+
const showFavoritesOnly = this.State.homeViewMode === 'favorites';
|
|
1012
|
+
const favSig = showFavoritesOnly ? this.State.favoriteEntities.map(f => f.entityId).join(',') : '';
|
|
1013
|
+
const key = `${filterText}::${showFavoritesOnly ? 1 : 0}::${favSig}`;
|
|
1014
|
+
if (this._flatFilteredEntitiesSource !== this.Entities || key !== this._flatFilteredEntitiesKey) {
|
|
1015
|
+
this._flatFilteredEntitiesSource = this.Entities;
|
|
1016
|
+
this._flatFilteredEntitiesKey = key;
|
|
1017
|
+
let result = this.Entities;
|
|
1018
|
+
if (showFavoritesOnly) {
|
|
1019
|
+
result = result.filter(e => this.IsEntityFavorited(e));
|
|
1020
|
+
}
|
|
1021
|
+
if (filterText) {
|
|
1022
|
+
result = result.filter(e => e.Name.toLowerCase().includes(filterText) ||
|
|
1023
|
+
e.DisplayNameOrName.toLowerCase().includes(filterText) ||
|
|
1024
|
+
(e.Description && e.Description.toLowerCase().includes(filterText)));
|
|
1025
|
+
}
|
|
1026
|
+
this._flatFilteredEntitiesCache = result;
|
|
1002
1027
|
}
|
|
1003
|
-
return
|
|
1028
|
+
return this._flatFilteredEntitiesCache;
|
|
1004
1029
|
}
|
|
1005
1030
|
/** @deprecated Use {@link FlatFilteredEntities}. */
|
|
1006
1031
|
get flatFilteredEntities() {
|
|
@@ -1098,33 +1123,42 @@ let DataExplorerDashboardComponent = class DataExplorerDashboardComponent extend
|
|
|
1098
1123
|
isQuickAccessSectionExpanded(sectionId) {
|
|
1099
1124
|
return this.IsQuickAccessSectionExpanded(sectionId);
|
|
1100
1125
|
}
|
|
1126
|
+
_uniqueRecentRecordEntitiesCache = [];
|
|
1127
|
+
_uniqueRecentRecordEntitiesSource = null;
|
|
1128
|
+
_uniqueRecentRecordEntitiesLength = -1;
|
|
1101
1129
|
/**
|
|
1102
1130
|
* Get unique entities from recent records for the filter strip.
|
|
1103
1131
|
* Returns up to 5 entities, sorted by frequency in the recent records.
|
|
1104
1132
|
*/
|
|
1105
1133
|
get UniqueRecentRecordEntities() {
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1134
|
+
if (this._uniqueRecentRecordEntitiesSource !== this.RecentRecords ||
|
|
1135
|
+
this._uniqueRecentRecordEntitiesLength !== this.RecentRecords.length) {
|
|
1136
|
+
this._uniqueRecentRecordEntitiesSource = this.RecentRecords;
|
|
1137
|
+
this._uniqueRecentRecordEntitiesLength = this.RecentRecords.length;
|
|
1138
|
+
const entityCounts = new Map();
|
|
1139
|
+
for (const record of this.RecentRecords) {
|
|
1140
|
+
const existing = entityCounts.get(record.entityId);
|
|
1141
|
+
if (existing) {
|
|
1142
|
+
existing.count++;
|
|
1143
|
+
}
|
|
1144
|
+
else {
|
|
1145
|
+
entityCounts.set(record.entityId, {
|
|
1146
|
+
entityId: record.entityId,
|
|
1147
|
+
entityName: record.entityName,
|
|
1148
|
+
count: 1
|
|
1149
|
+
});
|
|
1150
|
+
}
|
|
1118
1151
|
}
|
|
1152
|
+
// Convert to array, sort by count (descending), take first 5
|
|
1153
|
+
this._uniqueRecentRecordEntitiesCache = Array.from(entityCounts.values())
|
|
1154
|
+
.sort((a, b) => b.count - a.count)
|
|
1155
|
+
.slice(0, 5)
|
|
1156
|
+
.map(e => ({
|
|
1157
|
+
...e,
|
|
1158
|
+
icon: this.GetEntityIconById(e.entityId)
|
|
1159
|
+
}));
|
|
1119
1160
|
}
|
|
1120
|
-
|
|
1121
|
-
return Array.from(entityCounts.values())
|
|
1122
|
-
.sort((a, b) => b.count - a.count)
|
|
1123
|
-
.slice(0, 5)
|
|
1124
|
-
.map(e => ({
|
|
1125
|
-
...e,
|
|
1126
|
-
icon: this.GetEntityIconById(e.entityId)
|
|
1127
|
-
}));
|
|
1161
|
+
return this._uniqueRecentRecordEntitiesCache;
|
|
1128
1162
|
}
|
|
1129
1163
|
/** @deprecated Use {@link UniqueRecentRecordEntities}. */
|
|
1130
1164
|
get uniqueRecentRecordEntities() {
|
|
@@ -1141,6 +1175,9 @@ let DataExplorerDashboardComponent = class DataExplorerDashboardComponent extend
|
|
|
1141
1175
|
get showRecentRecordsEntityFilter() {
|
|
1142
1176
|
return this.ShowRecentRecordsEntityFilter;
|
|
1143
1177
|
}
|
|
1178
|
+
_filteredRecentRecordsCache = [];
|
|
1179
|
+
_filteredRecentRecordsSource = null;
|
|
1180
|
+
_filteredRecentRecordsFilter = null;
|
|
1144
1181
|
/**
|
|
1145
1182
|
* Get filtered recent records based on entity filter.
|
|
1146
1183
|
*/
|
|
@@ -1148,7 +1185,13 @@ let DataExplorerDashboardComponent = class DataExplorerDashboardComponent extend
|
|
|
1148
1185
|
if (!this.RecentRecordsEntityFilter) {
|
|
1149
1186
|
return this.RecentRecords;
|
|
1150
1187
|
}
|
|
1151
|
-
|
|
1188
|
+
if (this._filteredRecentRecordsSource !== this.RecentRecords ||
|
|
1189
|
+
this._filteredRecentRecordsFilter !== this.RecentRecordsEntityFilter) {
|
|
1190
|
+
this._filteredRecentRecordsSource = this.RecentRecords;
|
|
1191
|
+
this._filteredRecentRecordsFilter = this.RecentRecordsEntityFilter;
|
|
1192
|
+
this._filteredRecentRecordsCache = this.RecentRecords.filter(r => r.entityId === this.RecentRecordsEntityFilter);
|
|
1193
|
+
}
|
|
1194
|
+
return this._filteredRecentRecordsCache;
|
|
1152
1195
|
}
|
|
1153
1196
|
/** @deprecated Use {@link FilteredRecentRecords}. */
|
|
1154
1197
|
get filteredRecentRecords() {
|
|
@@ -1226,6 +1269,8 @@ let DataExplorerDashboardComponent = class DataExplorerDashboardComponent extend
|
|
|
1226
1269
|
}
|
|
1227
1270
|
async ngOnInit() {
|
|
1228
1271
|
super.ngOnInit();
|
|
1272
|
+
// Wired before the async setup below, so a delete during a slow load isn't missed.
|
|
1273
|
+
this.listenForRecordDeletes();
|
|
1229
1274
|
try {
|
|
1230
1275
|
// Ensure UserInfoEngine is configured before we try to access user settings
|
|
1231
1276
|
// This prevents race conditions where we try to load default view settings
|
|
@@ -1318,12 +1363,24 @@ let DataExplorerDashboardComponent = class DataExplorerDashboardComponent extend
|
|
|
1318
1363
|
// etc. without the template ever computing time during change detection. Updating the stored
|
|
1319
1364
|
// field + running detectChanges here (rather than calling a Date.now()-based method from the
|
|
1320
1365
|
// template binding) is what prevents the ExpressionChangedAfterItHasBeenCheckedError (NG0100).
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
this.refreshRecentRecordTimes()
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1366
|
+
const startTimer = () => {
|
|
1367
|
+
this.recentTimeRefreshTimer = setInterval(() => {
|
|
1368
|
+
if (this.RecentRecords.length > 0 && this.refreshRecentRecordTimes()) {
|
|
1369
|
+
if (this.ngZone) {
|
|
1370
|
+
this.ngZone.run(() => this.cdr.detectChanges());
|
|
1371
|
+
}
|
|
1372
|
+
else {
|
|
1373
|
+
this.cdr.detectChanges();
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
}, 30000);
|
|
1377
|
+
};
|
|
1378
|
+
if (this.ngZone) {
|
|
1379
|
+
this.ngZone.runOutsideAngular(startTimer);
|
|
1380
|
+
}
|
|
1381
|
+
else {
|
|
1382
|
+
startTimer();
|
|
1383
|
+
}
|
|
1327
1384
|
// Subscribe to favorite records changes
|
|
1328
1385
|
this.StateService.FavoriteRecords
|
|
1329
1386
|
.pipe(takeUntil(this.destroy$))
|
|
@@ -2045,6 +2102,7 @@ let DataExplorerDashboardComponent = class DataExplorerDashboardComponent extend
|
|
|
2045
2102
|
this.SelectedEntity = null;
|
|
2046
2103
|
this.SelectedRecord = null;
|
|
2047
2104
|
this.DetailPanelEntity = null;
|
|
2105
|
+
this.SelectedAlternateBaseView = null;
|
|
2048
2106
|
this.StateService.selectEntity(null);
|
|
2049
2107
|
this.StateService.closeDetailPanel();
|
|
2050
2108
|
this.cdr.detectChanges();
|
|
@@ -2239,6 +2297,7 @@ let DataExplorerDashboardComponent = class DataExplorerDashboardComponent extend
|
|
|
2239
2297
|
// state would leak into the new entity's query (e.g., ORDER BY FirstName on Groups).
|
|
2240
2298
|
// The workspace resets its own view/grid state when its [Entity] input changes.
|
|
2241
2299
|
this.SelectedViewEntity = null;
|
|
2300
|
+
this.SelectedAlternateBaseView = null;
|
|
2242
2301
|
this.SelectedEntity = entity;
|
|
2243
2302
|
this.reconcileViewModeForEntity(entity);
|
|
2244
2303
|
this.StateService.selectEntity(entity.Name);
|
|
@@ -2295,6 +2354,9 @@ let DataExplorerDashboardComponent = class DataExplorerDashboardComponent extend
|
|
|
2295
2354
|
*/
|
|
2296
2355
|
OnWorkspaceViewSelected(view) {
|
|
2297
2356
|
this.SelectedViewEntity = view;
|
|
2357
|
+
if (view) {
|
|
2358
|
+
this.SelectedAlternateBaseView = null;
|
|
2359
|
+
}
|
|
2298
2360
|
this.StateService.selectView(view?.ID ?? null);
|
|
2299
2361
|
if (view && view.SmartFilterEnabled && view.SmartFilterPrompt) {
|
|
2300
2362
|
this.StateService.setSmartFilterPrompt(view.SmartFilterPrompt);
|
|
@@ -2311,6 +2373,14 @@ let DataExplorerDashboardComponent = class DataExplorerDashboardComponent extend
|
|
|
2311
2373
|
onWorkspaceViewSelected(view) {
|
|
2312
2374
|
return this.OnWorkspaceViewSelected(view);
|
|
2313
2375
|
}
|
|
2376
|
+
/**
|
|
2377
|
+
* Handle an Alternate Base View selection emitted by the workspace's view selector.
|
|
2378
|
+
*/
|
|
2379
|
+
OnWorkspaceAlternateBaseViewChanged(abv) {
|
|
2380
|
+
this.SelectedAlternateBaseView = abv;
|
|
2381
|
+
this.pushCurrentStateToUrl();
|
|
2382
|
+
this.cdr.detectChanges();
|
|
2383
|
+
}
|
|
2314
2384
|
/**
|
|
2315
2385
|
* Handle the workspace's "open view in tab" request — route via NavigationService.
|
|
2316
2386
|
*/
|
|
@@ -2466,6 +2536,12 @@ let DataExplorerDashboardComponent = class DataExplorerDashboardComponent extend
|
|
|
2466
2536
|
if (this.pendingRecordSelection && this.SelectedEntity) {
|
|
2467
2537
|
const recordId = this.pendingRecordSelection;
|
|
2468
2538
|
this.pendingRecordSelection = null; // Clear it so we don't keep trying
|
|
2539
|
+
if (this.wasDeletedInSession(this.SelectedEntity, recordId)) {
|
|
2540
|
+
// A replayed key for a record deleted in this session — nothing to select.
|
|
2541
|
+
this.StateService.closeDetailPanel();
|
|
2542
|
+
this.cdr.detectChanges();
|
|
2543
|
+
return;
|
|
2544
|
+
}
|
|
2469
2545
|
// Try to find the record by primary key or concatenated string
|
|
2470
2546
|
const entity = this.SelectedEntity;
|
|
2471
2547
|
const record = event.records.find(r => {
|
|
@@ -2484,7 +2560,8 @@ let DataExplorerDashboardComponent = class DataExplorerDashboardComponent extend
|
|
|
2484
2560
|
}
|
|
2485
2561
|
}
|
|
2486
2562
|
// Restore selected record if we have a persisted selectedRecordId
|
|
2487
|
-
else if (this.State.selectedRecordId && this.State.detailPanelOpen && !this.SelectedRecord && this.SelectedEntity
|
|
2563
|
+
else if (this.State.selectedRecordId && this.State.detailPanelOpen && !this.SelectedRecord && this.SelectedEntity
|
|
2564
|
+
&& !this.wasDeletedInSession(this.SelectedEntity, this.State.selectedRecordId)) {
|
|
2488
2565
|
const entity = this.SelectedEntity;
|
|
2489
2566
|
const record = event.records.find(r => buildPkString(r, entity) === this.State.selectedRecordId);
|
|
2490
2567
|
if (record) {
|
|
@@ -2525,6 +2602,51 @@ let DataExplorerDashboardComponent = class DataExplorerDashboardComponent extend
|
|
|
2525
2602
|
onDetailPanelClosed() {
|
|
2526
2603
|
return this.OnDetailPanelClosed();
|
|
2527
2604
|
}
|
|
2605
|
+
/**
|
|
2606
|
+
* Keep the preview and the grid honest when records are deleted — from a form in another tab,
|
|
2607
|
+
* a custom form, or any other `BaseEntity.Delete()`; and, for the grid, by another session.
|
|
2608
|
+
*/
|
|
2609
|
+
listenForRecordDeletes() {
|
|
2610
|
+
MJGlobal.Instance.GetEventListener()
|
|
2611
|
+
.pipe(takeUntil(this.destroy$))
|
|
2612
|
+
.subscribe(event => {
|
|
2613
|
+
// Read the key now: right after raising 'delete' the entity is reset and its key is gone.
|
|
2614
|
+
const deleted = GetDeletedRecordFromEvent(event);
|
|
2615
|
+
if (deleted) {
|
|
2616
|
+
this.OnRecordDeleted(deleted);
|
|
2617
|
+
}
|
|
2618
|
+
});
|
|
2619
|
+
this.gridRefreshOnDelete$
|
|
2620
|
+
.pipe(debounceTime(300), takeUntil(this.destroy$))
|
|
2621
|
+
.subscribe(() => this.ViewWorkspaceRef?.RefreshInPlace());
|
|
2622
|
+
}
|
|
2623
|
+
/**
|
|
2624
|
+
* A record was deleted. A delete in this session closes the preview if it shows that record,
|
|
2625
|
+
* and the record is remembered so a replayed `record=` param can't reopen it. A delete by
|
|
2626
|
+
* another session leaves the preview alone — the user may be reading it — as record tabs do.
|
|
2627
|
+
* Either way, a grid showing that entity reloads in place, keeping the user's page and sort.
|
|
2628
|
+
*/
|
|
2629
|
+
OnRecordDeleted(deleted) {
|
|
2630
|
+
if (!deleted.Remote) {
|
|
2631
|
+
this.deletedRecordKeys.add(this.deletedRecordKey(deleted.EntityName, deleted.RecordId));
|
|
2632
|
+
const panelEntity = this.DetailPanelEntity;
|
|
2633
|
+
if (this.SelectedRecord && panelEntity &&
|
|
2634
|
+
RecordIdentityMatches(panelEntity.Name, buildPkString(this.SelectedRecord, panelEntity), deleted)) {
|
|
2635
|
+
this.OnDetailPanelClosed();
|
|
2636
|
+
this.cdr.detectChanges();
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
if (this.SelectedEntity?.Name.trim().toLowerCase() === deleted.EntityName.trim().toLowerCase()) {
|
|
2640
|
+
this.gridRefreshOnDelete$.next();
|
|
2641
|
+
}
|
|
2642
|
+
}
|
|
2643
|
+
deletedRecordKey(entityName, recordId) {
|
|
2644
|
+
return `${entityName.trim().toLowerCase()}::${NormalizeRecordIdForMatch(recordId)}`;
|
|
2645
|
+
}
|
|
2646
|
+
/** True when this session deleted the record a URL / deep-link key names. */
|
|
2647
|
+
wasDeletedInSession(entity, recordKey) {
|
|
2648
|
+
return !!entity && this.deletedRecordKeys.has(this.deletedRecordKey(entity.Name, recordKey));
|
|
2649
|
+
}
|
|
2528
2650
|
/**
|
|
2529
2651
|
* Handle opening a record in full view (from detail panel)
|
|
2530
2652
|
* Uses detailPanelEntity since the panel may be showing a different entity than the grid
|
|
@@ -2743,6 +2865,7 @@ let DataExplorerDashboardComponent = class DataExplorerDashboardComponent extend
|
|
|
2743
2865
|
// Reset counts before setting entity to prevent stale data display
|
|
2744
2866
|
this.resetRecordCounts();
|
|
2745
2867
|
this.SelectedEntity = entity;
|
|
2868
|
+
this.SelectedAlternateBaseView = deepLink.abv || null;
|
|
2746
2869
|
this.StateService.selectEntity(entity.Name);
|
|
2747
2870
|
// Apply filter if specified (to smart filter state, not user search)
|
|
2748
2871
|
if (deepLink.filter) {
|
|
@@ -2761,6 +2884,15 @@ let DataExplorerDashboardComponent = class DataExplorerDashboardComponent extend
|
|
|
2761
2884
|
}
|
|
2762
2885
|
/** Record ID to select once data loads (from deep link) */
|
|
2763
2886
|
pendingRecordSelection = null;
|
|
2887
|
+
/**
|
|
2888
|
+
* Records deleted in this session, as `entity::id` keys (see {@link deletedRecordKey}). Leaving a
|
|
2889
|
+
* deleted record's tab returns here by replaying the query params captured when it was opened —
|
|
2890
|
+
* `record=<its id>` included — so every path that would re-select a record from a key checks this
|
|
2891
|
+
* first, or the preview reopens on a record that no longer exists.
|
|
2892
|
+
*/
|
|
2893
|
+
deletedRecordKeys = new Set();
|
|
2894
|
+
/** Grid reloads asked for by deletes, debounced so a burst of deletes reloads once. */
|
|
2895
|
+
gridRefreshOnDelete$ = new Subject();
|
|
2764
2896
|
// ========================================
|
|
2765
2897
|
// BREADCRUMB NAVIGATION
|
|
2766
2898
|
// ========================================
|
|
@@ -2787,6 +2919,10 @@ let DataExplorerDashboardComponent = class DataExplorerDashboardComponent extend
|
|
|
2787
2919
|
// ========================================
|
|
2788
2920
|
// HELPERS
|
|
2789
2921
|
// ========================================
|
|
2922
|
+
_allowedEntityNamesCache = null;
|
|
2923
|
+
_allowedEntityNamesFilterSource = null;
|
|
2924
|
+
_allowedEntityNamesEntitiesSource = null;
|
|
2925
|
+
_allowedEntityNamesEntitiesLen = -1;
|
|
2790
2926
|
/**
|
|
2791
2927
|
* Get the set of allowed entity names for filtering favorites/recents.
|
|
2792
2928
|
* Returns null if no filter is active (all entities allowed).
|
|
@@ -2795,7 +2931,16 @@ let DataExplorerDashboardComponent = class DataExplorerDashboardComponent extend
|
|
|
2795
2931
|
if (!this.EntityFilter) {
|
|
2796
2932
|
return null;
|
|
2797
2933
|
}
|
|
2798
|
-
|
|
2934
|
+
if (this._allowedEntityNamesFilterSource !== this.EntityFilter ||
|
|
2935
|
+
this._allowedEntityNamesEntitiesSource !== this.Entities ||
|
|
2936
|
+
this._allowedEntityNamesEntitiesLen !== this.Entities.length ||
|
|
2937
|
+
this._allowedEntityNamesCache === null) {
|
|
2938
|
+
this._allowedEntityNamesFilterSource = this.EntityFilter;
|
|
2939
|
+
this._allowedEntityNamesEntitiesSource = this.Entities;
|
|
2940
|
+
this._allowedEntityNamesEntitiesLen = this.Entities.length;
|
|
2941
|
+
this._allowedEntityNamesCache = new Set(this.Entities.map(e => e.Name));
|
|
2942
|
+
}
|
|
2943
|
+
return this._allowedEntityNamesCache;
|
|
2799
2944
|
}
|
|
2800
2945
|
/** @deprecated Use {@link AllowedEntityNames}. */
|
|
2801
2946
|
get allowedEntityNames() {
|
|
@@ -2857,7 +3002,8 @@ let DataExplorerDashboardComponent = class DataExplorerDashboardComponent extend
|
|
|
2857
3002
|
const record = params['record'] || null;
|
|
2858
3003
|
const filterParam = params['filter'] || null;
|
|
2859
3004
|
const viewId = params['viewId'] || null;
|
|
2860
|
-
|
|
3005
|
+
const abv = params['abv'] || null;
|
|
3006
|
+
if (!entity && !record && !filterParam && !viewId && !abv) {
|
|
2861
3007
|
return null;
|
|
2862
3008
|
}
|
|
2863
3009
|
// ViewTypeID-only: the active view type is persisted via UserView.ViewTypeID / per-user settings
|
|
@@ -2866,7 +3012,8 @@ let DataExplorerDashboardComponent = class DataExplorerDashboardComponent extend
|
|
|
2866
3012
|
entity: entity || undefined,
|
|
2867
3013
|
record: record || undefined,
|
|
2868
3014
|
filter: filterParam || undefined,
|
|
2869
|
-
viewId: viewId || undefined
|
|
3015
|
+
viewId: viewId || undefined,
|
|
3016
|
+
abv: abv || undefined
|
|
2870
3017
|
};
|
|
2871
3018
|
}
|
|
2872
3019
|
/**
|
|
@@ -2914,7 +3061,8 @@ let DataExplorerDashboardComponent = class DataExplorerDashboardComponent extend
|
|
|
2914
3061
|
entity: this.State.selectedEntityName || null,
|
|
2915
3062
|
record: (this.State.selectedRecordId && hasEntity) ? this.State.selectedRecordId : null,
|
|
2916
3063
|
filter: null, // Never in URL — filters live in saved views (DB), not query strings
|
|
2917
|
-
viewId: hasViewId ? this.State.selectedViewId : null
|
|
3064
|
+
viewId: hasViewId ? this.State.selectedViewId : null,
|
|
3065
|
+
abv: (!hasViewId && hasEntity && this.SelectedAlternateBaseView) ? this.SelectedAlternateBaseView : null
|
|
2918
3066
|
};
|
|
2919
3067
|
this.UpdateQueryParams(queryParams);
|
|
2920
3068
|
}
|
|
@@ -2929,6 +3077,8 @@ let DataExplorerDashboardComponent = class DataExplorerDashboardComponent extend
|
|
|
2929
3077
|
const entity = this.Entities.find(e => e.Name.toLowerCase() === urlState.entity.toLowerCase());
|
|
2930
3078
|
if (entity) {
|
|
2931
3079
|
const entityChanged = this.SelectedEntity?.Name !== entity.Name;
|
|
3080
|
+
const previousAbv = this.SelectedAlternateBaseView;
|
|
3081
|
+
this.SelectedAlternateBaseView = urlState.viewId ? null : (urlState.abv || null);
|
|
2932
3082
|
if (entityChanged) {
|
|
2933
3083
|
// Entity changed - reset counts and select new entity
|
|
2934
3084
|
this.resetRecordCounts();
|
|
@@ -2952,6 +3102,9 @@ let DataExplorerDashboardComponent = class DataExplorerDashboardComponent extend
|
|
|
2952
3102
|
this.StateService.selectView(null);
|
|
2953
3103
|
}
|
|
2954
3104
|
}
|
|
3105
|
+
if (!entityChanged && previousAbv !== this.SelectedAlternateBaseView) {
|
|
3106
|
+
this.pushCurrentStateToUrl();
|
|
3107
|
+
}
|
|
2955
3108
|
// Filters live in saved views (DB), never in URL query strings.
|
|
2956
3109
|
// Clear smart filter on entity change when no specific view is selected.
|
|
2957
3110
|
if (entityChanged && !urlState.viewId) {
|
|
@@ -2962,8 +3115,14 @@ let DataExplorerDashboardComponent = class DataExplorerDashboardComponent extend
|
|
|
2962
3115
|
this.LiveFilterText = '';
|
|
2963
3116
|
this.DebouncedFilterText = '';
|
|
2964
3117
|
}
|
|
2965
|
-
// Handle record selection
|
|
2966
|
-
|
|
3118
|
+
// Handle record selection. A key for a record deleted in this session (replayed when its
|
|
3119
|
+
// tab closes and returns here) is treated as no record.
|
|
3120
|
+
if (urlState.record && this.wasDeletedInSession(entity, urlState.record)) {
|
|
3121
|
+
this.SelectedRecord = null;
|
|
3122
|
+
this.DetailPanelEntity = null;
|
|
3123
|
+
this.StateService.closeDetailPanel();
|
|
3124
|
+
}
|
|
3125
|
+
else if (urlState.record && !entityChanged && this.isSelectedRecord(urlState.record)) {
|
|
2967
3126
|
// Already showing this record — nothing to re-resolve
|
|
2968
3127
|
}
|
|
2969
3128
|
else if (urlState.record) {
|
|
@@ -3328,9 +3487,15 @@ let DataExplorerDashboardComponent = class DataExplorerDashboardComponent extend
|
|
|
3328
3487
|
* record set loads and on a timer so the labels stay current.
|
|
3329
3488
|
*/
|
|
3330
3489
|
refreshRecentRecordTimes() {
|
|
3490
|
+
let changed = false;
|
|
3331
3491
|
for (const record of this.RecentRecords) {
|
|
3332
|
-
|
|
3492
|
+
const next = this.formatRelativeTime(record.latestAt);
|
|
3493
|
+
if (record.relativeTime !== next) {
|
|
3494
|
+
record.relativeTime = next;
|
|
3495
|
+
changed = true;
|
|
3496
|
+
}
|
|
3333
3497
|
}
|
|
3498
|
+
return changed;
|
|
3334
3499
|
}
|
|
3335
3500
|
/**
|
|
3336
3501
|
* Format relative time for display (e.g., "2 hours ago").
|
|
@@ -3390,7 +3555,7 @@ let DataExplorerDashboardComponent = class DataExplorerDashboardComponent extend
|
|
|
3390
3555
|
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.ViewWorkspaceRef = _t.first);
|
|
3391
3556
|
} }, hostBindings: function DataExplorerDashboardComponent_HostBindings(rf, ctx) { if (rf & 1) {
|
|
3392
3557
|
i0.ɵɵlistener("keydown", function DataExplorerDashboardComponent_keydown_HostBindingHandler($event) { return ctx.handleKeyboardShortcut($event); }, i0.ɵɵresolveDocument);
|
|
3393
|
-
} }, inputs: { EntityFilter: "EntityFilter", entityFilter: "entityFilter", DeepLink: "DeepLink", deepLink: "deepLink", ContextName: "ContextName", contextName: "contextName", ContextIcon: "ContextIcon", contextIcon: "contextIcon", InitialQueryParams: "InitialQueryParams", initialQueryParams: "initialQueryParams" }, outputs: { DisplayNameChanged: "DisplayNameChanged" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature, i0.ɵɵNgOnChangesFeature], decls: 18, vars: 13, consts: [["filterInput", ""], [1, "data-explorer-container"], [1, "navigation-panel", 3, "collapsed", "width"], [1, "content-area"], [1, "breadcrumb-bar"], [1, "content-header"], [1, "header-left"], [1, "header-center"], [1, "smart-filter-container"], [1, "header-right"], ["title", "Recents & Favorites", 1, "header-action-btn", 3, "active"], ["role", "status", 1, "dx-moved-banner", 3, "panel-open"], [1, "content-body"], [1, "home-view-concept-d"], [3, "Entity", "AutoSaveView", "SelectedView", "FilterText", "SelectedRecordId", "ViewerConfig"], [1, "detail-panel", 3, "width"], [1, "navigation-panel"], [3, "entitySelected", "toggleCollapse", "sectionToggled", "openRecord", "selectRecord", "expandAndFocus", "entities", "appEntityGroups", "selectedEntityName", "favorites", "recentItems", "collapsed", "allowedEntityNames", "favoritesSectionExpanded", "recentSectionExpanded", "entitiesSectionExpanded", "viewsSectionExpanded"], [1, "breadcrumb-item", 3, "click", "title"], [1, "breadcrumb-icon", 3, "class"], [1, "breadcrumb-label"], [1, "fa-solid", "fa-chevron-right", "breadcrumb-separator"], [1, "breadcrumb-icon"], [1, "entity-icon"], [1, "entity-title"], [1, "record-count"], [1, "entity-icon", 3, "class"], [1, "fa-solid", "fa-search", "filter-icon"], ["type", "text", "placeholder", "Filter records... (press / to focus)", 1, "smart-filter-input", 3, "input", "value"], [1, "clear-filter-btn"], [1, "clear-filter-btn", 3, "click"], [1, "fa-solid", "fa-times"], ["title", "Recents & Favorites", 1, "header-action-btn", 3, "click"], [1, "fa-solid", "fa-clock-rotate-left"], [1, "qa-badge"], ["role", "status", 1, "dx-moved-banner"], ["aria-hidden", "true", 1, "fa-solid", "fa-gauge-high"], ["mjButton", "", "size", "sm", 3, "click"], ["mjButton", "", "size", "sm", "variant", "flat", 3, "click"], [1, "loading-state"], ["text", "Loading your data...", "size", "medium"], [1, "home-main-area"], [1, "search-hero"], [1, "search-hero-container"], [1, "fa-solid", "fa-magnifying-glass", "search-hero-icon"], ["type", "text", "placeholder", "Search your data...", 1, "search-hero-input", 3, "ngModelChange", "ngModel"], [1, "search-hero-clear"], [1, "search-hero-shortcut"], [1, "search-meta-row"], [1, "search-entity-count"], [1, "pill-toggle"], [1, "pill-btn", 3, "click"], [1, "fa-solid", "fa-star"], [1, "entity-groups-area"], [1, "entity-item-grid"], ["Variant", "no-results", "Title", "No matches found", 3, "Message"], ["Icon", "fa-solid fa-database", "Title", "Nothing here yet", "Message", "This app doesn't have any data set up yet. An administrator can add data for you to explore here."], [1, "quick-access-panel"], [1, "qa-header"], [1, "qa-close-btn", 3, "click"], [1, "fa-solid", "fa-xmark"], [1, "qa-body"], [3, "ExpandedChange", "Expanded"], ["mjAccordionTitle", ""], [1, "qa-item"], [1, "qa-empty"], [1, "search-hero-clear", 3, "click"], [1, "entity-item", 3, "title"], [1, "entity-item", 3, "click", "title"], [1, "entity-item-icon"], [1, "entity-item-text"], [1, "entity-item-name"], [1, "entity-item-desc"], [1, "entity-item-fav", 3, "click", "title"], [1, "app-group"], [1, "app-group-header", 3, "click"], [1, "app-group-icon"], [1, "app-group-name"], [1, "app-group-count"], [1, "fa-solid", "fa-chevron-right", "app-group-chevron"], [1, "app-group-entities"], [1, "fa-solid", "fa-clock-rotate-left", "qa-section-icon"], [1, "mj-accordion-badge", "mj-accordion-badge--right"], [1, "qa-item", 3, "click"], [1, "qa-item-icon"], [1, "qa-item-info"], [1, "qa-item-name"], [1, "qa-item-meta"], [1, "qa-item-time"], [1, "fa-solid", "fa-table", "qa-section-icon"], [1, "fa-solid", "fa-star", "qa-section-icon", 2, "color", "var(--mj-status-warning)"], [3, "SelectedViewChange", "ViewSelected", "OpenViewInTabRequested", "OpenRecordRequested", "OpenRelatedRecordRequested", "CreateNewRecordRequested", "RecordSelected", "AfterViewSave", "AfterViewDelete", "FilterTextChanged", "DataLoaded", "FilteredCountChanged", "Entity", "AutoSaveView", "SelectedView", "FilterText", "SelectedRecordId", "ViewerConfig"], [1, "detail-panel"], [3, "close", "openRecord", "navigateToRelated", "openRelatedRecord", "openForeignKeyRecord", "entity", "record"]], template: function DataExplorerDashboardComponent_Template(rf, ctx) { if (rf & 1) {
|
|
3558
|
+
} }, inputs: { EntityFilter: "EntityFilter", entityFilter: "entityFilter", DeepLink: "DeepLink", deepLink: "deepLink", ContextName: "ContextName", contextName: "contextName", ContextIcon: "ContextIcon", contextIcon: "contextIcon", InitialQueryParams: "InitialQueryParams", initialQueryParams: "initialQueryParams" }, outputs: { DisplayNameChanged: "DisplayNameChanged" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature, i0.ɵɵNgOnChangesFeature], decls: 18, vars: 13, consts: [["filterInput", ""], [1, "data-explorer-container"], [1, "navigation-panel", 3, "collapsed", "width"], [1, "content-area"], [1, "breadcrumb-bar"], [1, "content-header"], [1, "header-left"], [1, "header-center"], [1, "smart-filter-container"], [1, "header-right"], ["title", "Recents & Favorites", 1, "header-action-btn", 3, "active"], ["role", "status", 1, "dx-moved-banner", 3, "panel-open"], [1, "content-body"], [1, "home-view-concept-d"], [3, "Entity", "AutoSaveView", "SelectedView", "AlternateBaseView", "FilterText", "SelectedRecordId", "ViewerConfig"], [1, "detail-panel", 3, "width"], [1, "navigation-panel"], [3, "entitySelected", "toggleCollapse", "sectionToggled", "openRecord", "selectRecord", "expandAndFocus", "entities", "appEntityGroups", "selectedEntityName", "favorites", "recentItems", "collapsed", "allowedEntityNames", "favoritesSectionExpanded", "recentSectionExpanded", "entitiesSectionExpanded", "viewsSectionExpanded"], [1, "breadcrumb-item", 3, "click", "title"], [1, "breadcrumb-icon", 3, "class"], [1, "breadcrumb-label"], [1, "fa-solid", "fa-chevron-right", "breadcrumb-separator"], [1, "breadcrumb-icon"], [1, "entity-icon"], [1, "entity-title"], [1, "record-count"], [1, "entity-icon", 3, "class"], [1, "fa-solid", "fa-search", "filter-icon"], ["type", "text", "placeholder", "Filter records... (press / to focus)", 1, "smart-filter-input", 3, "input", "value"], [1, "clear-filter-btn"], [1, "clear-filter-btn", 3, "click"], [1, "fa-solid", "fa-times"], ["title", "Recents & Favorites", 1, "header-action-btn", 3, "click"], [1, "fa-solid", "fa-clock-rotate-left"], [1, "qa-badge"], ["role", "status", 1, "dx-moved-banner"], ["aria-hidden", "true", 1, "fa-solid", "fa-gauge-high"], ["mjButton", "", "size", "sm", 3, "click"], ["mjButton", "", "size", "sm", "variant", "flat", 3, "click"], [1, "loading-state"], ["text", "Loading your data...", "size", "medium"], [1, "home-main-area"], [1, "search-hero"], [1, "search-hero-container"], [1, "fa-solid", "fa-magnifying-glass", "search-hero-icon"], ["type", "text", "placeholder", "Search your data...", 1, "search-hero-input", 3, "ngModelChange", "ngModel"], [1, "search-hero-clear"], [1, "search-hero-shortcut"], [1, "search-meta-row"], [1, "search-entity-count"], [1, "pill-toggle"], [1, "pill-btn", 3, "click"], [1, "fa-solid", "fa-star"], [1, "entity-groups-area"], [1, "entity-item-grid"], ["Variant", "no-results", "Title", "No matches found", 3, "Message"], ["Icon", "fa-solid fa-database", "Title", "Nothing here yet", "Message", "This app doesn't have any data set up yet. An administrator can add data for you to explore here."], [1, "quick-access-panel"], [1, "qa-header"], [1, "qa-close-btn", 3, "click"], [1, "fa-solid", "fa-xmark"], [1, "qa-body"], [3, "ExpandedChange", "Expanded"], ["mjAccordionTitle", ""], [1, "qa-item"], [1, "qa-empty"], [1, "search-hero-clear", 3, "click"], [1, "entity-item", 3, "title"], [1, "entity-item", 3, "click", "title"], [1, "entity-item-icon"], [1, "entity-item-text"], [1, "entity-item-name"], [1, "entity-item-desc"], [1, "entity-item-fav", 3, "click", "title"], [1, "app-group"], [1, "app-group-header", 3, "click"], [1, "app-group-icon"], [1, "app-group-name"], [1, "app-group-count"], [1, "fa-solid", "fa-chevron-right", "app-group-chevron"], [1, "app-group-entities"], [1, "fa-solid", "fa-clock-rotate-left", "qa-section-icon"], [1, "mj-accordion-badge", "mj-accordion-badge--right"], [1, "qa-item", 3, "click"], [1, "qa-item-icon"], [1, "qa-item-info"], [1, "qa-item-name"], [1, "qa-item-meta"], [1, "qa-item-time"], [1, "fa-solid", "fa-table", "qa-section-icon"], [1, "fa-solid", "fa-star", "qa-section-icon", 2, "color", "var(--mj-status-warning)"], [3, "SelectedViewChange", "ViewSelected", "AlternateBaseViewChange", "OpenViewInTabRequested", "OpenRecordRequested", "OpenRelatedRecordRequested", "CreateNewRecordRequested", "RecordSelected", "AfterViewSave", "AfterViewDelete", "FilterTextChanged", "DataLoaded", "FilteredCountChanged", "Entity", "AutoSaveView", "SelectedView", "AlternateBaseView", "FilterText", "SelectedRecordId", "ViewerConfig"], [1, "detail-panel"], [3, "close", "openRecord", "navigateToRelated", "openRelatedRecord", "openForeignKeyRecord", "entity", "record"]], template: function DataExplorerDashboardComponent_Template(rf, ctx) { if (rf & 1) {
|
|
3394
3559
|
i0.ɵɵelementStart(0, "div", 1);
|
|
3395
3560
|
i0.ɵɵconditionalCreate(1, DataExplorerDashboardComponent_Conditional_1_Template, 2, 16, "div", 2);
|
|
3396
3561
|
i0.ɵɵelementStart(2, "div", 3);
|
|
@@ -3407,7 +3572,7 @@ let DataExplorerDashboardComponent = class DataExplorerDashboardComponent extend
|
|
|
3407
3572
|
i0.ɵɵelementEnd()();
|
|
3408
3573
|
i0.ɵɵconditionalCreate(13, DataExplorerDashboardComponent_Conditional_13_Template, 10, 2, "div", 11);
|
|
3409
3574
|
i0.ɵɵelementStart(14, "div", 12);
|
|
3410
|
-
i0.ɵɵconditionalCreate(15, DataExplorerDashboardComponent_Conditional_15_Template, 3, 1, "div", 13)(16, DataExplorerDashboardComponent_Conditional_16_Template, 1,
|
|
3575
|
+
i0.ɵɵconditionalCreate(15, DataExplorerDashboardComponent_Conditional_15_Template, 3, 1, "div", 13)(16, DataExplorerDashboardComponent_Conditional_16_Template, 1, 7, "mj-view-workspace", 14);
|
|
3411
3576
|
i0.ɵɵelementEnd()();
|
|
3412
3577
|
i0.ɵɵconditionalCreate(17, DataExplorerDashboardComponent_Conditional_17_Template, 2, 4, "div", 15);
|
|
3413
3578
|
i0.ɵɵelementEnd();
|
|
@@ -3434,7 +3599,7 @@ let DataExplorerDashboardComponent = class DataExplorerDashboardComponent extend
|
|
|
3434
3599
|
i0.ɵɵconditional(!ctx.selectedEntity ? 15 : 16);
|
|
3435
3600
|
i0.ɵɵadvance(2);
|
|
3436
3601
|
i0.ɵɵconditional(ctx.state.detailPanelOpen && ctx.selectedRecord ? 17 : -1);
|
|
3437
|
-
} }, dependencies: [i3.DefaultValueAccessor, i3.NgControlStatus, i3.NgModel, i4.MJEmptyStateComponent, i4.MJAccordionPanelComponent, i4.MJAccordionTitleDirective, i4.MJButtonDirective, i5.EntityRecordDetailPanelComponent, i5.ViewWorkspaceComponent, i2.LoadingComponent, i6.NavigationPanelComponent, i7.DecimalPipe], styles: [".data-explorer-container[_ngcontent-%COMP%] {\n display: flex;\n height: 100%;\n width: 100%;\n background: var(--mj-bg-surface-card);\n overflow: hidden;\n}\n\n.navigation-panel[_ngcontent-%COMP%] {\n flex-shrink: 0;\n height: 100%;\n background: var(--mj-bg-surface);\n border-right: 1px solid var(--mj-border-default);\n transition: width 0.2s ease-in-out;\n overflow: hidden;\n box-shadow: 2px 0 8px rgba(0, 0, 0, 0.04);\n}\n.navigation-panel.collapsed[_ngcontent-%COMP%] {\n width: 48px;\n}\n\n.content-area[_ngcontent-%COMP%] {\n flex: 1;\n display: flex;\n flex-direction: column;\n height: 100%;\n min-width: 0;\n overflow: hidden;\n background: var(--mj-bg-surface-card);\n}\n\n\n.breadcrumb-bar[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 24px;\n background: var(--mj-bg-surface);\n border-bottom: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n font-size: 13px;\n min-height: 40px;\n}\n\n.breadcrumb-item[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 6px;\n color: var(--mj-text-secondary);\n padding: 4px 8px;\n border-radius: 4px;\n transition: all 0.15s ease;\n max-width: 200px;\n}\n\n.breadcrumb-item.clickable[_ngcontent-%COMP%] {\n cursor: pointer;\n}\n\n.breadcrumb-item.clickable[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-brand-primary);\n}\n\n.breadcrumb-item.current[_ngcontent-%COMP%] {\n color: var(--mj-text-primary);\n font-weight: 500;\n cursor: default;\n}\n\n.breadcrumb-icon[_ngcontent-%COMP%] {\n font-size: 12px;\n flex-shrink: 0;\n}\n\n.breadcrumb-label[_ngcontent-%COMP%] {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.breadcrumb-separator[_ngcontent-%COMP%] {\n font-size: 10px;\n color: var(--mj-border-strong);\n flex-shrink: 0;\n}\n\n.content-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px 24px;\n background: var(--mj-bg-surface);\n border-bottom: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n gap: 24px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);\n position: relative;\n z-index: 2; \n}\n\n.header-left[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n flex-shrink: 0;\n flex-wrap: wrap;\n}\n\n\n.header-left[_ngcontent-%COMP%] mj-view-selector {\n margin-left: 8px;\n}\n\n.entity-icon[_ngcontent-%COMP%] {\n font-size: 20px;\n color: var(--mj-brand-primary);\n}\n\n.entity-title[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 20px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.record-count[_ngcontent-%COMP%] {\n font-size: 13px;\n color: var(--mj-text-secondary);\n font-weight: 500;\n}\n\n.header-center[_ngcontent-%COMP%] {\n flex: 1;\n max-width: 600px;\n}\n\n.smart-filter-container[_ngcontent-%COMP%] {\n position: relative;\n width: 100%;\n}\n\n.smart-filter-input[_ngcontent-%COMP%] {\n width: 100%;\n padding: 10px 40px 10px 16px;\n border: 1px solid var(--mj-border-strong);\n border-radius: 8px;\n font-size: 14px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n transition: all 0.15s ease;\n}\n.smart-filter-input[_ngcontent-%COMP%]:focus {\n outline: none;\n border-color: var(--mj-brand-primary);\n background: var(--mj-bg-surface);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n}\n.smart-filter-input[_ngcontent-%COMP%]::placeholder {\n color: var(--mj-text-muted);\n}\n\n.clear-filter-btn[_ngcontent-%COMP%] {\n position: absolute;\n right: 8px;\n top: 50%;\n transform: translateY(-50%);\n width: 28px;\n height: 28px;\n border: none;\n background: transparent;\n border-radius: 50%;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-muted);\n transition: all 0.15s ease;\n}\n.clear-filter-btn[_ngcontent-%COMP%]:hover {\n background: var(--mj-border-default);\n color: var(--mj-text-primary);\n}\n\n.header-right[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 16px;\n flex-shrink: 0;\n}\n\n.view-mode-toggle[_ngcontent-%COMP%] {\n display: flex;\n background: var(--mj-bg-surface-sunken);\n border-radius: 8px;\n padding: 3px;\n}\n\n.toggle-btn[_ngcontent-%COMP%] {\n width: 36px;\n height: 32px;\n border: none;\n background: transparent;\n border-radius: 6px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-secondary);\n transition: all 0.15s ease;\n}\n.toggle-btn[_ngcontent-%COMP%]:hover {\n color: var(--mj-text-primary);\n}\n.toggle-btn.active[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface);\n color: var(--mj-brand-primary);\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n}\n\n\n.header-action-btn[_ngcontent-%COMP%] {\n position: relative;\n width: 36px;\n height: 36px;\n border: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n border-radius: 8px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-secondary);\n transition: all 0.15s ease;\n margin-left: 8px;\n}\n.header-action-btn[_ngcontent-%COMP%]:hover:not(.disabled) {\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 5%, var(--mj-bg-surface));\n}\n.header-action-btn.disabled[_ngcontent-%COMP%] {\n opacity: 0.5;\n cursor: not-allowed;\n}\n.header-action-btn[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 14px;\n}\n.selection-badge[_ngcontent-%COMP%] {\n position: absolute;\n top: -6px;\n right: -6px;\n min-width: 18px;\n height: 18px;\n padding: 0 5px;\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n font-size: 11px;\n font-weight: 600;\n border-radius: 9px;\n display: flex;\n align-items: center;\n justify-content: center;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);\n}\n\n\n.date-field-selector-container[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n position: relative;\n}\n\n.date-field-backdrop[_ngcontent-%COMP%] {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n z-index: 999;\n background: transparent;\n}\n\n.date-field-selector-wrapper[_ngcontent-%COMP%] {\n position: relative;\n}\n\n.date-field-selector-button[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 6px 12px;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n cursor: pointer;\n font-size: 13px;\n color: var(--mj-text-primary);\n transition: all 0.15s ease;\n min-width: 120px;\n max-width: 200px;\n}\n\n.date-field-selector-button[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: var(--mj-bg-surface-hover);\n border-color: var(--mj-border-strong);\n}\n\n.date-field-selector-button[_ngcontent-%COMP%]:disabled {\n cursor: default;\n opacity: 0.8;\n}\n\n.date-field-selector-button.open[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface-active);\n border-color: var(--mj-brand-primary);\n}\n\n.date-field-icon[_ngcontent-%COMP%] {\n color: var(--mj-text-secondary);\n font-size: 12px;\n}\n\n.date-field-name[_ngcontent-%COMP%] {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n text-align: left;\n}\n\n.date-field-arrow[_ngcontent-%COMP%] {\n color: var(--mj-text-secondary);\n font-size: 10px;\n transition: transform 0.2s ease;\n}\n\n.date-field-arrow.rotated[_ngcontent-%COMP%] {\n transform: rotate(180deg);\n}\n\n.date-field-dropdown-panel[_ngcontent-%COMP%] {\n position: absolute;\n top: calc(100% + 4px);\n left: 0;\n min-width: 180px;\n max-width: 280px;\n max-height: 300px;\n overflow-y: auto;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);\n z-index: 1000;\n}\n\n.date-field-dropdown-item[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 10px 14px;\n cursor: pointer;\n transition: background 0.1s ease;\n font-size: 13px;\n}\n\n.date-field-dropdown-item[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-card);\n}\n\n.date-field-dropdown-item.selected[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n}\n\n.date-field-dropdown-item.selected[_ngcontent-%COMP%]:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n}\n\n.date-field-dropdown-item[_ngcontent-%COMP%] .item-icon[_ngcontent-%COMP%] {\n color: var(--mj-text-secondary);\n font-size: 13px;\n width: 16px;\n text-align: center;\n}\n\n.date-field-dropdown-item[_ngcontent-%COMP%] .item-name[_ngcontent-%COMP%] {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.date-field-dropdown-item[_ngcontent-%COMP%] .selected-check[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n font-size: 12px;\n}\n\n\n\n.view-specific-controls[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 4px;\n padding: 3px 8px;\n background: var(--mj-bg-surface-sunken);\n border-radius: 8px;\n border: 1px solid var(--mj-border-subtle);\n}\n\n.view-specific-btn[_ngcontent-%COMP%] {\n width: 30px;\n height: 30px;\n border: none;\n background: transparent;\n border-radius: 6px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-muted);\n transition: all 0.15s ease;\n}\n\n.view-specific-btn[_ngcontent-%COMP%]:hover {\n color: var(--mj-text-secondary);\n background: var(--mj-bg-surface-hover);\n}\n\n\n.timeline-orientation-toggle[_ngcontent-%COMP%] {\n display: flex;\n background: var(--mj-bg-surface-sunken);\n border-radius: 8px;\n padding: 3px;\n}\n\n.content-body[_ngcontent-%COMP%] {\n display: flex;\n flex: 1 1 0%;\n overflow: hidden;\n \n\n\n\n\n\n\n\n isolation: isolate;\n position: relative; \n z-index: 3; \n}\n\n\n\n\n\n\n.dx-moved-banner[_ngcontent-%COMP%] {\n display: flex;\n flex-wrap: wrap;\n flex-shrink: 0;\n align-items: center;\n gap: 10px;\n padding: 10px 14px;\n margin: 12px 24px;\n border-radius: 10px;\n background: var(--mj-status-info-bg);\n color: var(--mj-status-info-text);\n font-size: 13px;\n transition: margin-right 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n\n.content-header.home-header[_ngcontent-%COMP%] + .dx-moved-banner[_ngcontent-%COMP%] {\n margin-top: 0;\n}\n\n\n.dx-moved-banner.panel-open[_ngcontent-%COMP%] {\n margin-right: 344px;\n}\n\n\n.dx-moved-banner[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n flex: 1 1 200px;\n}\n\n.loading-container[_ngcontent-%COMP%], \n.loading-state[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n gap: 16px;\n background: var(--mj-bg-surface);\n border-radius: 8px;\n}\n\n.loading-spinner[_ngcontent-%COMP%] {\n font-size: 32px;\n color: var(--mj-brand-primary);\n}\n\n.loading-message[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 14px;\n color: var(--mj-text-secondary);\n}\n\n\n\n\n\n\n.home-view-concept-d[_ngcontent-%COMP%] {\n flex: 1;\n display: flex;\n overflow: hidden;\n}\n\n.home-main-area[_ngcontent-%COMP%] {\n flex: 1;\n overflow-y: auto;\n transition: margin-right 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.home-main-area.panel-open[_ngcontent-%COMP%] {\n margin-right: 320px;\n}\n\n\n\n\n\n.search-hero[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface);\n padding: 36px 40px 24px;\n text-align: center;\n}\n\n.search-hero-container[_ngcontent-%COMP%] {\n position: relative;\n max-width: 600px;\n margin: 0 auto;\n}\n\n.search-hero-input[_ngcontent-%COMP%] {\n width: 100%;\n padding: 14px 18px 14px 44px;\n font-size: 15px;\n border: 2px solid var(--mj-border-default);\n border-radius: 12px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n outline: none;\n transition: all 0.2s;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06);\n font-family: inherit;\n}\n\n.search-hero-input[_ngcontent-%COMP%]:focus {\n border-color: var(--mj-brand-primary);\n box-shadow: 0 12px 40px rgba(0, 0, 0, 0.1), 0 0 0 4px color-mix(in srgb, var(--mj-brand-primary) 8%, transparent);\n}\n\n.search-hero-input[_ngcontent-%COMP%]::placeholder {\n color: var(--mj-text-primary);\n}\n\n.search-hero-icon[_ngcontent-%COMP%] {\n position: absolute;\n left: 16px;\n top: 50%;\n transform: translateY(-50%);\n color: var(--mj-text-primary);\n font-size: 15px;\n pointer-events: none;\n}\n\n.search-hero-shortcut[_ngcontent-%COMP%] {\n position: absolute;\n right: 14px;\n top: 50%;\n transform: translateY(-50%);\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n padding: 2px 7px;\n border-radius: 5px;\n font-size: 12px;\n color: var(--mj-text-muted);\n}\n\n.search-hero-clear[_ngcontent-%COMP%] {\n position: absolute;\n right: 14px;\n top: 50%;\n transform: translateY(-50%);\n width: 28px;\n height: 28px;\n border: none;\n background: transparent;\n border-radius: 50%;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-muted);\n transition: all 0.15s ease;\n}\n\n.search-hero-clear[_ngcontent-%COMP%]:hover {\n background: var(--mj-border-default);\n color: var(--mj-text-primary);\n}\n\n\n.search-meta-row[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 12px;\n margin-top: 16px;\n}\n\n.search-entity-count[_ngcontent-%COMP%] {\n font-size: 13px;\n color: var(--mj-text-muted);\n}\n\n.pill-toggle[_ngcontent-%COMP%] {\n display: flex;\n gap: 6px;\n}\n\n.pill-btn[_ngcontent-%COMP%] {\n padding: 6px 16px;\n border-radius: 20px;\n border: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n font-size: 13px;\n color: var(--mj-text-secondary);\n cursor: pointer;\n transition: all 0.15s;\n font-family: inherit;\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n.pill-btn[_ngcontent-%COMP%]:hover {\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-primary);\n}\n\n.pill-btn.active[_ngcontent-%COMP%] {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n border-color: var(--mj-brand-primary);\n}\n\n.pill-btn[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 11px;\n}\n\n\n\n\n\n.entity-groups-area[_ngcontent-%COMP%] {\n padding: 12px 40px 60px;\n max-width: 1200px;\n margin: 0 auto;\n}\n\n.app-group[_ngcontent-%COMP%] {\n margin-bottom: 4px;\n}\n\n.app-group-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 12px 14px;\n cursor: pointer;\n border-radius: 8px;\n transition: background 0.12s;\n user-select: none;\n}\n\n.app-group-header[_ngcontent-%COMP%]:hover {\n background: rgba(0, 0, 0, 0.025);\n}\n\n.app-group-icon[_ngcontent-%COMP%] {\n width: 38px;\n height: 38px;\n border-radius: 10px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 17px;\n flex-shrink: 0;\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n}\n\n.app-group-name[_ngcontent-%COMP%] {\n font-size: 21px;\n font-weight: 600;\n flex: 1;\n}\n\n.app-group-count[_ngcontent-%COMP%] {\n font-size: 12px;\n color: var(--mj-text-muted);\n background: var(--mj-bg-surface-card);\n padding: 2px 9px;\n border-radius: 10px;\n}\n\n.app-group-chevron[_ngcontent-%COMP%] {\n color: var(--mj-text-muted);\n font-size: 12px;\n transition: transform 0.2s;\n}\n\n.app-group-chevron.expanded[_ngcontent-%COMP%] {\n transform: rotate(90deg);\n}\n\n.app-group-entities[_ngcontent-%COMP%] {\n padding: 4px 14px 10px 60px;\n}\n\n\n\n\n\n.entity-item-grid[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));\n gap: 12px;\n}\n\n.entity-item[_ngcontent-%COMP%] {\n display: flex;\n align-items: flex-start;\n gap: 12px;\n padding: 10px 14px;\n border-radius: 8px;\n cursor: pointer;\n transition: all 0.12s;\n border: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n}\n\n.entity-item[_ngcontent-%COMP%]:hover {\n border-color: color-mix(in srgb, var(--mj-brand-primary) 40%, var(--mj-bg-surface));\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);\n background: color-mix(in srgb, var(--mj-brand-primary) 5%, var(--mj-bg-surface));\n}\n\n.entity-item-icon[_ngcontent-%COMP%] {\n width: 32px;\n height: 32px;\n border-radius: 8px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 14px;\n color: var(--mj-text-secondary);\n background: var(--mj-bg-surface-card);\n flex-shrink: 0;\n}\n\n.entity-item[_ngcontent-%COMP%]:hover .entity-item-icon[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n}\n\n.entity-item-text[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.entity-item-name[_ngcontent-%COMP%] {\n font-size: 14px;\n font-weight: 500;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.entity-item-desc[_ngcontent-%COMP%] {\n font-size: 12px;\n color: var(--mj-text-muted);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n line-height: 1.3;\n}\n\n.entity-item-fav[_ngcontent-%COMP%] {\n color: var(--mj-text-muted);\n opacity: 0;\n transition: opacity 0.12s;\n font-size: 11px;\n cursor: pointer;\n flex-shrink: 0;\n background: none;\n border: none;\n padding: 4px;\n align-self: center;\n}\n\n.entity-item[_ngcontent-%COMP%]:hover .entity-item-fav[_ngcontent-%COMP%] {\n opacity: 1;\n}\n\n.entity-item-fav.favorited[_ngcontent-%COMP%] {\n opacity: 1;\n color: var(--mj-status-warning);\n}\n\n\n\n\n\n.quick-access-panel[_ngcontent-%COMP%] {\n position: fixed;\n top: 60px;\n right: 0;\n width: 320px;\n height: calc(100% - 60px);\n background: var(--mj-bg-surface);\n border-left: 1px solid var(--mj-border-default);\n z-index: 50;\n transform: translateX(100%);\n transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n display: flex;\n flex-direction: column;\n}\n\n.quick-access-panel.open[_ngcontent-%COMP%] {\n transform: translateX(0);\n}\n\n.qa-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 12px 16px;\n border-bottom: 1px solid var(--mj-border-default);\n}\n\n.qa-header[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] {\n font-size: 14px;\n font-weight: 600;\n margin: 0;\n}\n\n.qa-close-btn[_ngcontent-%COMP%] {\n background: none;\n border: none;\n color: var(--mj-text-muted);\n cursor: pointer;\n font-size: 14px;\n padding: 4px;\n border-radius: 4px;\n}\n\n.qa-close-btn[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-card);\n}\n\n.qa-body[_ngcontent-%COMP%] {\n flex: 1;\n overflow-y: auto;\n}\n\n.qa-item[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 8px;\n border-radius: 8px;\n cursor: pointer;\n transition: background 0.08s;\n}\n\n.qa-item[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-card);\n}\n\n.qa-item-icon[_ngcontent-%COMP%] {\n width: 28px;\n height: 28px;\n border-radius: 6px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 12px;\n background: var(--mj-bg-surface-card);\n color: var(--mj-text-secondary);\n flex-shrink: 0;\n}\n\n.qa-item-info[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 0;\n}\n\n.qa-item-name[_ngcontent-%COMP%] {\n font-size: 13px;\n font-weight: 500;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.qa-item-meta[_ngcontent-%COMP%] {\n font-size: 11px;\n color: var(--mj-text-muted);\n}\n\n.qa-item-time[_ngcontent-%COMP%] {\n font-size: 11px;\n color: var(--mj-text-muted);\n white-space: nowrap;\n}\n\n.qa-empty[_ngcontent-%COMP%] {\n padding: 20px 16px;\n text-align: center;\n color: var(--mj-text-muted);\n font-size: 13px;\n}\n\n\n.qa-badge[_ngcontent-%COMP%] {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n font-size: 10px;\n font-weight: 600;\n padding: 0 5px;\n border-radius: 8px;\n min-width: 16px;\n text-align: center;\n}\n\n.detail-panel[_ngcontent-%COMP%] {\n flex-shrink: 0;\n height: 100%;\n background: var(--mj-bg-surface);\n border-left: 1px solid var(--mj-border-default);\n box-shadow: -4px 0 16px rgba(0, 0, 0, 0.08);\n overflow: hidden;\n animation: _ngcontent-%COMP%_slideIn 0.2s ease-out;\n display: flex;\n flex-direction: column;\n}\n\n.detail-panel[_ngcontent-%COMP%] mj-entity-record-detail-panel[_ngcontent-%COMP%] {\n flex: 1;\n min-height: 0;\n overflow: hidden;\n}\n\n@keyframes _ngcontent-%COMP%_slideIn {\n from {\n transform: translateX(100%);\n opacity: 0;\n }\n to {\n transform: translateX(0);\n opacity: 1;\n }\n}\n\n\n.detail-panel-actions[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n background: var(--mj-bg-surface-card);\n border-bottom: 1px solid var(--mj-border-default);\n}\n\n.detail-action-btn[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 6px 12px;\n border: none;\n border-radius: 6px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);\n}\n\n.detail-action-btn[_ngcontent-%COMP%]:hover {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n transform: translateY(-1px);\n box-shadow: 0 2px 4px color-mix(in srgb, var(--mj-brand-primary) 20%, transparent);\n}\n\n.detail-action-btn[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 12px;\n}\n\n[_nghost-%COMP%] mj-explorer-grid-view, \n[_nghost-%COMP%] mj-explorer-cards-view {\n flex: 1;\n display: flex;\n flex-direction: column;\n min-height: 0;\n}\n\n\n\n\n\n\n.content-header.home-header[_ngcontent-%COMP%] {\n border-bottom: none;\n background: transparent;\n box-shadow: none;\n padding: 16px 24px 8px;\n}\n\n.content-body.home-content[_ngcontent-%COMP%] {\n padding: 0;\n}\n\n\n.filter-icon[_ngcontent-%COMP%] {\n position: absolute;\n left: 14px;\n top: 50%;\n transform: translateY(-50%);\n color: var(--mj-text-muted);\n font-size: 14px;\n pointer-events: none;\n}\n\n.smart-filter-container[_ngcontent-%COMP%] .smart-filter-input[_ngcontent-%COMP%] {\n padding-left: 40px;\n}\n\n\n\n\n\n.view-mode-toggle[_ngcontent-%COMP%] .toggle-btn[_ngcontent-%COMP%] {\n width: auto;\n padding: 0 12px;\n gap: 6px;\n}\n\n.toggle-label[_ngcontent-%COMP%] {\n font-size: 12px;\n font-weight: 500;\n}\n\n\n\n\n\n@media (max-width: 1200px) {\n .entity-item-grid[_ngcontent-%COMP%] {\n grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));\n }\n}\n\n@media (max-width: 900px) {\n .content-header[_ngcontent-%COMP%] {\n flex-wrap: wrap;\n gap: 12px;\n }\n\n .header-center[_ngcontent-%COMP%] {\n order: 3;\n flex-basis: 100%;\n max-width: 100%;\n }\n\n .search-hero[_ngcontent-%COMP%] {\n padding: 24px 16px 16px;\n }\n\n .entity-groups-area[_ngcontent-%COMP%] {\n padding: 8px 16px 40px;\n }\n\n .app-group-entities[_ngcontent-%COMP%] {\n padding-left: 36px;\n }\n\n .home-main-area.panel-open[_ngcontent-%COMP%] {\n margin-right: 0;\n }\n\n .quick-access-panel[_ngcontent-%COMP%] {\n width: 100%;\n }\n\n \n .dx-moved-banner.panel-open[_ngcontent-%COMP%] {\n margin-right: 24px;\n }\n}\n\n@media (max-width: 600px) {\n .data-explorer-container[_ngcontent-%COMP%] {\n flex-direction: column;\n }\n\n .navigation-panel[_ngcontent-%COMP%] {\n display: none;\n }\n\n .content-header[_ngcontent-%COMP%] {\n padding: 12px 16px;\n }\n\n .content-body[_ngcontent-%COMP%] {\n padding: 12px 16px;\n }\n\n \n .content-header[_ngcontent-%COMP%]:not(.home-header) + .dx-moved-banner[_ngcontent-%COMP%] {\n margin-left: 16px;\n margin-right: 16px;\n }\n\n .entity-item-grid[_ngcontent-%COMP%] {\n grid-template-columns: 1fr;\n }\n\n .view-mode-toggle[_ngcontent-%COMP%] {\n display: none;\n }\n\n .app-group-name[_ngcontent-%COMP%] {\n font-size: 17px;\n }\n\n .entity-item-name[_ngcontent-%COMP%] {\n font-size: 14px;\n }\n}"], data: { animation: [
|
|
3602
|
+
} }, dependencies: [i3.DefaultValueAccessor, i3.NgControlStatus, i3.NgModel, i4.MJEmptyStateComponent, i4.MJAccordionPanelComponent, i4.MJAccordionTitleDirective, i4.MJButtonDirective, i5.EntityRecordDetailPanelComponent, i5.ViewWorkspaceComponent, i2.LoadingComponent, i6.NavigationPanelComponent, i7.DecimalPipe], styles: [".data-explorer-container[_ngcontent-%COMP%] {\n display: flex;\n height: 100%;\n width: 100%;\n background: var(--mj-bg-surface-card);\n overflow: hidden;\n}\n\n.navigation-panel[_ngcontent-%COMP%] {\n flex-shrink: 0;\n height: 100%;\n background: var(--mj-bg-surface);\n border-right: 1px solid var(--mj-border-default);\n transition: width 0.2s ease-in-out;\n overflow: hidden;\n box-shadow: 2px 0 8px rgba(0, 0, 0, 0.04);\n \n\n isolation: isolate;\n position: relative;\n z-index: 1;\n}\n.navigation-panel.collapsed[_ngcontent-%COMP%] {\n width: 48px;\n}\n\n.content-area[_ngcontent-%COMP%] {\n flex: 1;\n display: flex;\n flex-direction: column;\n height: 100%;\n min-width: 0;\n overflow: hidden;\n background: var(--mj-bg-surface-card);\n}\n\n\n.breadcrumb-bar[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 24px;\n background: var(--mj-bg-surface);\n border-bottom: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n font-size: 13px;\n min-height: 40px;\n}\n\n.breadcrumb-item[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 6px;\n color: var(--mj-text-secondary);\n padding: 4px 8px;\n border-radius: 4px;\n transition: all 0.15s ease;\n max-width: 200px;\n}\n\n.breadcrumb-item.clickable[_ngcontent-%COMP%] {\n cursor: pointer;\n}\n\n.breadcrumb-item.clickable[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-brand-primary);\n}\n\n.breadcrumb-item.current[_ngcontent-%COMP%] {\n color: var(--mj-text-primary);\n font-weight: 500;\n cursor: default;\n}\n\n.breadcrumb-icon[_ngcontent-%COMP%] {\n font-size: 12px;\n flex-shrink: 0;\n}\n\n.breadcrumb-label[_ngcontent-%COMP%] {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.breadcrumb-separator[_ngcontent-%COMP%] {\n font-size: 10px;\n color: var(--mj-border-strong);\n flex-shrink: 0;\n}\n\n.content-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px 24px;\n background: var(--mj-bg-surface);\n border-bottom: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n gap: 24px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);\n position: relative;\n z-index: 2;\n}\n\n.header-left[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n flex-shrink: 0;\n flex-wrap: wrap;\n}\n\n\n.header-left[_ngcontent-%COMP%] mj-view-selector {\n margin-left: 8px;\n}\n\n.entity-icon[_ngcontent-%COMP%] {\n font-size: 20px;\n color: var(--mj-brand-primary);\n}\n\n.entity-title[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 20px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.record-count[_ngcontent-%COMP%] {\n font-size: 13px;\n color: var(--mj-text-secondary);\n font-weight: 500;\n}\n\n.header-center[_ngcontent-%COMP%] {\n flex: 1;\n max-width: 600px;\n}\n\n.smart-filter-container[_ngcontent-%COMP%] {\n position: relative;\n width: 100%;\n}\n\n.smart-filter-input[_ngcontent-%COMP%] {\n width: 100%;\n padding: 10px 40px 10px 16px;\n border: 1px solid var(--mj-border-strong);\n border-radius: 8px;\n font-size: 14px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n transition: all 0.15s ease;\n}\n.smart-filter-input[_ngcontent-%COMP%]:focus {\n outline: none;\n border-color: var(--mj-brand-primary);\n background: var(--mj-bg-surface);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n}\n.smart-filter-input[_ngcontent-%COMP%]::placeholder {\n color: var(--mj-text-muted);\n}\n\n.clear-filter-btn[_ngcontent-%COMP%] {\n position: absolute;\n right: 8px;\n top: 50%;\n transform: translateY(-50%);\n width: 28px;\n height: 28px;\n border: none;\n background: transparent;\n border-radius: 50%;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-muted);\n transition: all 0.15s ease;\n}\n.clear-filter-btn[_ngcontent-%COMP%]:hover {\n background: var(--mj-border-default);\n color: var(--mj-text-primary);\n}\n\n.header-right[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 16px;\n flex-shrink: 0;\n}\n\n.view-mode-toggle[_ngcontent-%COMP%] {\n display: flex;\n background: var(--mj-bg-surface-sunken);\n border-radius: 8px;\n padding: 3px;\n}\n\n.toggle-btn[_ngcontent-%COMP%] {\n width: 36px;\n height: 32px;\n border: none;\n background: transparent;\n border-radius: 6px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-secondary);\n transition: all 0.15s ease;\n}\n.toggle-btn[_ngcontent-%COMP%]:hover {\n color: var(--mj-text-primary);\n}\n.toggle-btn.active[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface);\n color: var(--mj-brand-primary);\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n}\n\n\n.header-action-btn[_ngcontent-%COMP%] {\n position: relative;\n width: 36px;\n height: 36px;\n border: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n border-radius: 8px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-secondary);\n transition: all 0.15s ease;\n margin-left: 8px;\n}\n.header-action-btn[_ngcontent-%COMP%]:hover:not(.disabled) {\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 5%, var(--mj-bg-surface));\n}\n.header-action-btn.disabled[_ngcontent-%COMP%] {\n opacity: 0.5;\n cursor: not-allowed;\n}\n.header-action-btn[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 14px;\n}\n.selection-badge[_ngcontent-%COMP%] {\n position: absolute;\n top: -6px;\n right: -6px;\n min-width: 18px;\n height: 18px;\n padding: 0 5px;\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n font-size: 11px;\n font-weight: 600;\n border-radius: 9px;\n display: flex;\n align-items: center;\n justify-content: center;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);\n}\n\n\n.date-field-selector-container[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n position: relative;\n}\n\n.date-field-backdrop[_ngcontent-%COMP%] {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n z-index: 999;\n background: transparent;\n}\n\n.date-field-selector-wrapper[_ngcontent-%COMP%] {\n position: relative;\n}\n\n.date-field-selector-button[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 6px 12px;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n cursor: pointer;\n font-size: 13px;\n color: var(--mj-text-primary);\n transition: all 0.15s ease;\n min-width: 120px;\n max-width: 200px;\n}\n\n.date-field-selector-button[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: var(--mj-bg-surface-hover);\n border-color: var(--mj-border-strong);\n}\n\n.date-field-selector-button[_ngcontent-%COMP%]:disabled {\n cursor: default;\n opacity: 0.8;\n}\n\n.date-field-selector-button.open[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface-active);\n border-color: var(--mj-brand-primary);\n}\n\n.date-field-icon[_ngcontent-%COMP%] {\n color: var(--mj-text-secondary);\n font-size: 12px;\n}\n\n.date-field-name[_ngcontent-%COMP%] {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n text-align: left;\n}\n\n.date-field-arrow[_ngcontent-%COMP%] {\n color: var(--mj-text-secondary);\n font-size: 10px;\n transition: transform 0.2s ease;\n}\n\n.date-field-arrow.rotated[_ngcontent-%COMP%] {\n transform: rotate(180deg);\n}\n\n.date-field-dropdown-panel[_ngcontent-%COMP%] {\n position: absolute;\n top: calc(100% + 4px);\n left: 0;\n min-width: 180px;\n max-width: 280px;\n max-height: 300px;\n overflow-y: auto;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);\n z-index: 1000;\n}\n\n.date-field-dropdown-item[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 10px 14px;\n cursor: pointer;\n transition: background 0.1s ease;\n font-size: 13px;\n}\n\n.date-field-dropdown-item[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-card);\n}\n\n.date-field-dropdown-item.selected[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n}\n\n.date-field-dropdown-item.selected[_ngcontent-%COMP%]:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n}\n\n.date-field-dropdown-item[_ngcontent-%COMP%] .item-icon[_ngcontent-%COMP%] {\n color: var(--mj-text-secondary);\n font-size: 13px;\n width: 16px;\n text-align: center;\n}\n\n.date-field-dropdown-item[_ngcontent-%COMP%] .item-name[_ngcontent-%COMP%] {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.date-field-dropdown-item[_ngcontent-%COMP%] .selected-check[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n font-size: 12px;\n}\n\n\n\n.view-specific-controls[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 4px;\n padding: 3px 8px;\n background: var(--mj-bg-surface-sunken);\n border-radius: 8px;\n border: 1px solid var(--mj-border-subtle);\n}\n\n.view-specific-btn[_ngcontent-%COMP%] {\n width: 30px;\n height: 30px;\n border: none;\n background: transparent;\n border-radius: 6px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-muted);\n transition: all 0.15s ease;\n}\n\n.view-specific-btn[_ngcontent-%COMP%]:hover {\n color: var(--mj-text-secondary);\n background: var(--mj-bg-surface-hover);\n}\n\n\n.timeline-orientation-toggle[_ngcontent-%COMP%] {\n display: flex;\n background: var(--mj-bg-surface-sunken);\n border-radius: 8px;\n padding: 3px;\n}\n\n.content-body[_ngcontent-%COMP%] {\n display: flex;\n flex: 1 1 0%;\n overflow: hidden;\n \n\n\n\n\n position: relative;\n}\n\n\n\n\n\n\n.dx-moved-banner[_ngcontent-%COMP%] {\n display: flex;\n flex-wrap: wrap;\n flex-shrink: 0;\n align-items: center;\n gap: 10px;\n padding: 10px 14px;\n margin: 12px 24px;\n border-radius: 10px;\n background: var(--mj-status-info-bg);\n color: var(--mj-status-info-text);\n font-size: 13px;\n transition: margin-right 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n\n.content-header.home-header[_ngcontent-%COMP%] + .dx-moved-banner[_ngcontent-%COMP%] {\n margin-top: 0;\n}\n\n\n.dx-moved-banner.panel-open[_ngcontent-%COMP%] {\n margin-right: 344px;\n}\n\n\n.dx-moved-banner[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n flex: 1 1 200px;\n}\n\n.loading-container[_ngcontent-%COMP%], \n.loading-state[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n gap: 16px;\n background: var(--mj-bg-surface);\n border-radius: 8px;\n}\n\n.loading-spinner[_ngcontent-%COMP%] {\n font-size: 32px;\n color: var(--mj-brand-primary);\n}\n\n.loading-message[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 14px;\n color: var(--mj-text-secondary);\n}\n\n\n\n\n\n\n.home-view-concept-d[_ngcontent-%COMP%] {\n flex: 1;\n display: flex;\n overflow: hidden;\n}\n\n.home-main-area[_ngcontent-%COMP%] {\n flex: 1;\n overflow-y: auto;\n transition: margin-right 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.home-main-area.panel-open[_ngcontent-%COMP%] {\n margin-right: 320px;\n}\n\n\n\n\n\n.search-hero[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface);\n padding: 36px 40px 24px;\n text-align: center;\n}\n\n.search-hero-container[_ngcontent-%COMP%] {\n position: relative;\n max-width: 600px;\n margin: 0 auto;\n}\n\n.search-hero-input[_ngcontent-%COMP%] {\n width: 100%;\n padding: 14px 18px 14px 44px;\n font-size: 15px;\n border: 2px solid var(--mj-border-default);\n border-radius: 12px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n outline: none;\n transition: all 0.2s;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06);\n font-family: inherit;\n}\n\n.search-hero-input[_ngcontent-%COMP%]:focus {\n border-color: var(--mj-brand-primary);\n box-shadow: 0 12px 40px rgba(0, 0, 0, 0.1), 0 0 0 4px color-mix(in srgb, var(--mj-brand-primary) 8%, transparent);\n}\n\n.search-hero-input[_ngcontent-%COMP%]::placeholder {\n color: var(--mj-text-primary);\n}\n\n.search-hero-icon[_ngcontent-%COMP%] {\n position: absolute;\n left: 16px;\n top: 50%;\n transform: translateY(-50%);\n color: var(--mj-text-primary);\n font-size: 15px;\n pointer-events: none;\n}\n\n.search-hero-shortcut[_ngcontent-%COMP%] {\n position: absolute;\n right: 14px;\n top: 50%;\n transform: translateY(-50%);\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n padding: 2px 7px;\n border-radius: 5px;\n font-size: 12px;\n color: var(--mj-text-muted);\n}\n\n.search-hero-clear[_ngcontent-%COMP%] {\n position: absolute;\n right: 14px;\n top: 50%;\n transform: translateY(-50%);\n width: 28px;\n height: 28px;\n border: none;\n background: transparent;\n border-radius: 50%;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-muted);\n transition: all 0.15s ease;\n}\n\n.search-hero-clear[_ngcontent-%COMP%]:hover {\n background: var(--mj-border-default);\n color: var(--mj-text-primary);\n}\n\n\n.search-meta-row[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 12px;\n margin-top: 16px;\n}\n\n.search-entity-count[_ngcontent-%COMP%] {\n font-size: 13px;\n color: var(--mj-text-muted);\n}\n\n.pill-toggle[_ngcontent-%COMP%] {\n display: flex;\n gap: 6px;\n}\n\n.pill-btn[_ngcontent-%COMP%] {\n padding: 6px 16px;\n border-radius: 20px;\n border: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n font-size: 13px;\n color: var(--mj-text-secondary);\n cursor: pointer;\n transition: all 0.15s;\n font-family: inherit;\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n.pill-btn[_ngcontent-%COMP%]:hover {\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-primary);\n}\n\n.pill-btn.active[_ngcontent-%COMP%] {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n border-color: var(--mj-brand-primary);\n}\n\n.pill-btn[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 11px;\n}\n\n\n\n\n\n.entity-groups-area[_ngcontent-%COMP%] {\n padding: 12px 40px 60px;\n max-width: 1200px;\n margin: 0 auto;\n}\n\n.app-group[_ngcontent-%COMP%] {\n margin-bottom: 4px;\n}\n\n.app-group-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 12px 14px;\n cursor: pointer;\n border-radius: 8px;\n transition: background 0.12s;\n user-select: none;\n}\n\n.app-group-header[_ngcontent-%COMP%]:hover {\n background: rgba(0, 0, 0, 0.025);\n}\n\n.app-group-icon[_ngcontent-%COMP%] {\n width: 38px;\n height: 38px;\n border-radius: 10px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 17px;\n flex-shrink: 0;\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n}\n\n.app-group-name[_ngcontent-%COMP%] {\n font-size: 21px;\n font-weight: 600;\n flex: 1;\n}\n\n.app-group-count[_ngcontent-%COMP%] {\n font-size: 12px;\n color: var(--mj-text-muted);\n background: var(--mj-bg-surface-card);\n padding: 2px 9px;\n border-radius: 10px;\n}\n\n.app-group-chevron[_ngcontent-%COMP%] {\n color: var(--mj-text-muted);\n font-size: 12px;\n transition: transform 0.2s;\n}\n\n.app-group-chevron.expanded[_ngcontent-%COMP%] {\n transform: rotate(90deg);\n}\n\n.app-group-entities[_ngcontent-%COMP%] {\n padding: 4px 14px 10px 60px;\n}\n\n\n\n\n\n.entity-item-grid[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));\n gap: 12px;\n}\n\n.entity-item[_ngcontent-%COMP%] {\n display: flex;\n align-items: flex-start;\n gap: 12px;\n padding: 10px 14px;\n border-radius: 8px;\n cursor: pointer;\n transition: all 0.12s;\n border: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n}\n\n.entity-item[_ngcontent-%COMP%]:hover {\n border-color: color-mix(in srgb, var(--mj-brand-primary) 40%, var(--mj-bg-surface));\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);\n background: color-mix(in srgb, var(--mj-brand-primary) 5%, var(--mj-bg-surface));\n}\n\n.entity-item-icon[_ngcontent-%COMP%] {\n width: 32px;\n height: 32px;\n border-radius: 8px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 14px;\n color: var(--mj-text-secondary);\n background: var(--mj-bg-surface-card);\n flex-shrink: 0;\n}\n\n.entity-item[_ngcontent-%COMP%]:hover .entity-item-icon[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n}\n\n.entity-item-text[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.entity-item-name[_ngcontent-%COMP%] {\n font-size: 14px;\n font-weight: 500;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.entity-item-desc[_ngcontent-%COMP%] {\n font-size: 12px;\n color: var(--mj-text-muted);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n line-height: 1.3;\n}\n\n.entity-item-fav[_ngcontent-%COMP%] {\n color: var(--mj-text-muted);\n opacity: 0;\n transition: opacity 0.12s;\n font-size: 11px;\n cursor: pointer;\n flex-shrink: 0;\n background: none;\n border: none;\n padding: 4px;\n align-self: center;\n}\n\n.entity-item[_ngcontent-%COMP%]:hover .entity-item-fav[_ngcontent-%COMP%] {\n opacity: 1;\n}\n\n.entity-item-fav.favorited[_ngcontent-%COMP%] {\n opacity: 1;\n color: var(--mj-status-warning);\n}\n\n\n\n\n\n.quick-access-panel[_ngcontent-%COMP%] {\n position: fixed;\n top: 60px;\n right: 0;\n width: 320px;\n height: calc(100% - 60px);\n background: var(--mj-bg-surface);\n border-left: 1px solid var(--mj-border-default);\n z-index: 50;\n transform: translateX(100%);\n transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n display: flex;\n flex-direction: column;\n}\n\n.quick-access-panel.open[_ngcontent-%COMP%] {\n transform: translateX(0);\n}\n\n.qa-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 12px 16px;\n border-bottom: 1px solid var(--mj-border-default);\n}\n\n.qa-header[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] {\n font-size: 14px;\n font-weight: 600;\n margin: 0;\n}\n\n.qa-close-btn[_ngcontent-%COMP%] {\n background: none;\n border: none;\n color: var(--mj-text-muted);\n cursor: pointer;\n font-size: 14px;\n padding: 4px;\n border-radius: 4px;\n}\n\n.qa-close-btn[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-card);\n}\n\n.qa-body[_ngcontent-%COMP%] {\n flex: 1;\n overflow-y: auto;\n}\n\n.qa-item[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 8px;\n border-radius: 8px;\n cursor: pointer;\n transition: background 0.08s;\n}\n\n.qa-item[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-card);\n}\n\n.qa-item-icon[_ngcontent-%COMP%] {\n width: 28px;\n height: 28px;\n border-radius: 6px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 12px;\n background: var(--mj-bg-surface-card);\n color: var(--mj-text-secondary);\n flex-shrink: 0;\n}\n\n.qa-item-info[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 0;\n}\n\n.qa-item-name[_ngcontent-%COMP%] {\n font-size: 13px;\n font-weight: 500;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.qa-item-meta[_ngcontent-%COMP%] {\n font-size: 11px;\n color: var(--mj-text-muted);\n}\n\n.qa-item-time[_ngcontent-%COMP%] {\n font-size: 11px;\n color: var(--mj-text-muted);\n white-space: nowrap;\n}\n\n.qa-empty[_ngcontent-%COMP%] {\n padding: 20px 16px;\n text-align: center;\n color: var(--mj-text-muted);\n font-size: 13px;\n}\n\n\n.qa-badge[_ngcontent-%COMP%] {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n font-size: 10px;\n font-weight: 600;\n padding: 0 5px;\n border-radius: 8px;\n min-width: 16px;\n text-align: center;\n}\n\n.detail-panel[_ngcontent-%COMP%] {\n flex-shrink: 0;\n height: 100%;\n background: var(--mj-bg-surface);\n border-left: 1px solid var(--mj-border-default);\n box-shadow: -4px 0 16px rgba(0, 0, 0, 0.08);\n overflow: hidden;\n animation: _ngcontent-%COMP%_slideIn 0.2s ease-out;\n display: flex;\n flex-direction: column;\n}\n\n.detail-panel[_ngcontent-%COMP%] mj-entity-record-detail-panel[_ngcontent-%COMP%] {\n flex: 1;\n min-height: 0;\n overflow: hidden;\n}\n\n@keyframes _ngcontent-%COMP%_slideIn {\n from {\n transform: translateX(100%);\n opacity: 0;\n }\n to {\n transform: translateX(0);\n opacity: 1;\n }\n}\n\n\n.detail-panel-actions[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n background: var(--mj-bg-surface-card);\n border-bottom: 1px solid var(--mj-border-default);\n}\n\n.detail-action-btn[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 6px 12px;\n border: none;\n border-radius: 6px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);\n}\n\n.detail-action-btn[_ngcontent-%COMP%]:hover {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n transform: translateY(-1px);\n box-shadow: 0 2px 4px color-mix(in srgb, var(--mj-brand-primary) 20%, transparent);\n}\n\n.detail-action-btn[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 12px;\n}\n\n[_nghost-%COMP%] mj-explorer-grid-view, \n[_nghost-%COMP%] mj-explorer-cards-view {\n flex: 1;\n display: flex;\n flex-direction: column;\n min-height: 0;\n}\n\n\n\n\n\n\n.content-header.home-header[_ngcontent-%COMP%] {\n border-bottom: none;\n background: transparent;\n box-shadow: none;\n padding: 16px 24px 8px;\n}\n\n.content-body.home-content[_ngcontent-%COMP%] {\n padding: 0;\n}\n\n\n.filter-icon[_ngcontent-%COMP%] {\n position: absolute;\n left: 14px;\n top: 50%;\n transform: translateY(-50%);\n color: var(--mj-text-muted);\n font-size: 14px;\n pointer-events: none;\n}\n\n.smart-filter-container[_ngcontent-%COMP%] .smart-filter-input[_ngcontent-%COMP%] {\n padding-left: 40px;\n}\n\n\n\n\n\n.view-mode-toggle[_ngcontent-%COMP%] .toggle-btn[_ngcontent-%COMP%] {\n width: auto;\n padding: 0 12px;\n gap: 6px;\n}\n\n.toggle-label[_ngcontent-%COMP%] {\n font-size: 12px;\n font-weight: 500;\n}\n\n\n\n\n\n@media (max-width: 1200px) {\n .entity-item-grid[_ngcontent-%COMP%] {\n grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));\n }\n}\n\n@media (max-width: 900px) {\n .content-header[_ngcontent-%COMP%] {\n flex-wrap: wrap;\n gap: 12px;\n }\n\n .header-center[_ngcontent-%COMP%] {\n order: 3;\n flex-basis: 100%;\n max-width: 100%;\n }\n\n .search-hero[_ngcontent-%COMP%] {\n padding: 24px 16px 16px;\n }\n\n .entity-groups-area[_ngcontent-%COMP%] {\n padding: 8px 16px 40px;\n }\n\n .app-group-entities[_ngcontent-%COMP%] {\n padding-left: 36px;\n }\n\n .home-main-area.panel-open[_ngcontent-%COMP%] {\n margin-right: 0;\n }\n\n .quick-access-panel[_ngcontent-%COMP%] {\n width: 100%;\n }\n\n \n .dx-moved-banner.panel-open[_ngcontent-%COMP%] {\n margin-right: 24px;\n }\n}\n\n@media (max-width: 600px) {\n .data-explorer-container[_ngcontent-%COMP%] {\n flex-direction: column;\n }\n\n .navigation-panel[_ngcontent-%COMP%] {\n display: none;\n }\n\n .content-header[_ngcontent-%COMP%] {\n padding: 12px 16px;\n }\n\n .content-body[_ngcontent-%COMP%] {\n padding: 12px 16px;\n }\n\n \n .content-header[_ngcontent-%COMP%]:not(.home-header) + .dx-moved-banner[_ngcontent-%COMP%] {\n margin-left: 16px;\n margin-right: 16px;\n }\n\n .entity-item-grid[_ngcontent-%COMP%] {\n grid-template-columns: 1fr;\n }\n\n .view-mode-toggle[_ngcontent-%COMP%] {\n display: none;\n }\n\n .app-group-name[_ngcontent-%COMP%] {\n font-size: 17px;\n }\n\n .entity-item-name[_ngcontent-%COMP%] {\n font-size: 14px;\n }\n}"], data: { animation: [
|
|
3438
3603
|
trigger('slideInLeft', [
|
|
3439
3604
|
transition(':enter', [
|
|
3440
3605
|
style({ transform: 'translateX(-100%)', opacity: 0 }),
|
|
@@ -3462,7 +3627,7 @@ export { DataExplorerDashboardComponent };
|
|
|
3462
3627
|
animate('200ms ease-in', style({ transform: 'translateX(-100%)', opacity: 0 }))
|
|
3463
3628
|
])
|
|
3464
3629
|
])
|
|
3465
|
-
], template: "<div class=\"data-explorer-container\">\n <!-- Navigation Panel (Left) - Hidden at home level, animated -->\n @if (!isAtHomeLevel) {\n <div\n class=\"navigation-panel\"\n [class.collapsed]=\"state.navigationPanelCollapsed\"\n [style.width.px]=\"state.navigationPanelCollapsed ? 48 : state.navigationPanelWidth\"\n [@slideInLeft]>\n\n <mj-explorer-navigation-panel\n [entities]=\"entities\"\n [appEntityGroups]=\"appEntityGroups\"\n [selectedEntityName]=\"state.selectedEntityName\"\n [favorites]=\"state.favorites\"\n [recentItems]=\"state.recentItems\"\n [collapsed]=\"state.navigationPanelCollapsed\"\n [allowedEntityNames]=\"allowedEntityNames\"\n [favoritesSectionExpanded]=\"state.favoritesSectionExpanded\"\n [recentSectionExpanded]=\"state.recentSectionExpanded\"\n [entitiesSectionExpanded]=\"state.entitiesSectionExpanded\"\n [viewsSectionExpanded]=\"state.viewsSectionExpanded\"\n (entitySelected)=\"onEntitySelected($event)\"\n (toggleCollapse)=\"toggleNavigationPanel()\"\n (sectionToggled)=\"stateService.toggleSection($event)\"\n (openRecord)=\"onOpenRecordFromNav($event)\"\n (selectRecord)=\"onSelectRecordFromNav($event)\"\n (expandAndFocus)=\"onExpandAndFocus($event)\">\n </mj-explorer-navigation-panel>\n </div>\n }\n\n <!-- Main Content Area -->\n <div class=\"content-area\">\n <!-- Breadcrumb Bar - Hidden at home level -->\n @if (!isAtHomeLevel && breadcrumbs.length > 0) {\n <div class=\"breadcrumb-bar\">\n @for (crumb of breadcrumbs; track crumb.label; let i = $index; let last = $last) {\n <span\n class=\"breadcrumb-item\"\n [class.clickable]=\"!last\"\n [class.current]=\"last\"\n (click)=\"onBreadcrumbClick(crumb, i)\"\n [title]=\"crumb.label\">\n @if (crumb.icon) {\n <i [class]=\"crumb.icon\" class=\"breadcrumb-icon\"></i>\n }\n <span class=\"breadcrumb-label\">{{ crumb.label }}</span>\n </span>\n @if (!last) {\n <i class=\"fa-solid fa-chevron-right breadcrumb-separator\"></i>\n }\n }\n </div>\n }\n\n <!-- Header -->\n <div class=\"content-header\" [class.home-header]=\"isAtHomeLevel\">\n <div class=\"header-left\">\n @if (selectedEntity) {\n <i [class]=\"getEntityIcon(selectedEntity)\" class=\"entity-icon\"></i>\n <h2 class=\"entity-title\">{{ selectedEntity.DisplayNameOrName }}</h2>\n @if (debouncedFilterText && filteredRecordCount !== totalRecordCount) {\n <span class=\"record-count\">{{ filteredRecordCount | number }} of {{ totalRecordCount | number }} records</span>\n } @else {\n <span class=\"record-count\">{{ totalRecordCount | number }} records</span>\n }\n\n <!-- View selector + view header now live inside mj-view-workspace (below). -->\n <!-- Add-to-List is now owned inside the grid plug-in renderer (it hosts its own dialog). -->\n } @else {\n @if (displayIcon) {\n <i [class]=\"displayIcon\" class=\"entity-icon\"></i>\n }\n <h2 class=\"entity-title\">{{ displayTitle }}</h2>\n <span class=\"record-count\">{{ entities.length }} record types available</span>\n }\n </div>\n\n <div class=\"header-center\">\n @if (selectedEntity) {\n <div class=\"smart-filter-container\">\n <i class=\"fa-solid fa-search filter-icon\"></i>\n <input\n #filterInput\n type=\"text\"\n class=\"smart-filter-input\"\n placeholder=\"Filter records... (press / to focus)\"\n [value]=\"liveFilterText\"\n (input)=\"onFilterInputChanged(filterInput.value)\"\n />\n @if (liveFilterText) {\n <button class=\"clear-filter-btn\" (click)=\"clearRecordFilter()\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n }\n </div>\n }\n <!-- Home-level search moved to search hero section -->\n </div>\n\n <div class=\"header-right\">\n @if (selectedEntity) {\n <!-- View-type switcher AND all view-specific controls (timeline date/orientation/sort,\n map render mode, etc.) now live inside the plug-in renderers hosted by\n mj-view-workspace. Nothing view-type-specific lives in this header anymore. -->\n }\n @if (!selectedEntity) {\n <!-- Quick Access panel toggle -->\n <button\n class=\"header-action-btn\"\n [class.active]=\"state.quickAccessPanelOpen\"\n (click)=\"toggleQuickAccessPanel()\"\n title=\"Recents & Favorites\">\n <i class=\"fa-solid fa-clock-rotate-left\"></i>\n @if (recentRecords.length + favoriteRecords.length > 0) {\n <span class=\"qa-badge\">{{ recentRecords.length + favoriteRecords.length }}</span>\n }\n </button>\n }\n </div>\n </div>\n\n <!-- Dashboards moved banner: shows with or without a selected entity.\n panel-open makes room for the Quick Access panel, which shows only on the loaded home view. -->\n @if (ShowDashboardsMovedBanner) {\n <div class=\"dx-moved-banner\" role=\"status\"\n [class.panel-open]=\"State.quickAccessPanelOpen && !SelectedEntity && !IsLoadingEntities\">\n <i class=\"fa-solid fa-gauge-high\" aria-hidden=\"true\"></i>\n <span><b>Dashboards moved.</b> They now live in the Dashboards app.</span>\n <button mjButton size=\"sm\" (click)=\"OpenDashboardsApp()\">Open Dashboards</button>\n <button mjButton size=\"sm\" variant=\"flat\" (click)=\"DismissDashboardsMovedBanner()\">Got it</button>\n </div>\n }\n\n <!-- Content Body - Using mj-entity-viewer composite -->\n <div class=\"content-body\" [class.home-content]=\"isAtHomeLevel\">\n @if (!selectedEntity) {\n <!-- Concept D: Application Groups + Search-First Home View -->\n <div class=\"home-view-concept-d\">\n @if (isLoadingEntities) {\n <div class=\"loading-state\">\n <mj-loading text=\"Loading your data...\" size=\"medium\"></mj-loading>\n </div>\n } @else {\n <div class=\"home-main-area\" [class.panel-open]=\"state.quickAccessPanelOpen\">\n <!-- Search Hero -->\n <div class=\"search-hero\">\n <div class=\"search-hero-container\">\n <i class=\"fa-solid fa-magnifying-glass search-hero-icon\"></i>\n <input\n #filterInput\n type=\"text\"\n class=\"search-hero-input\"\n placeholder=\"Search your data...\"\n [(ngModel)]=\"entityFilterText\"\n />\n @if (entityFilterText) {\n <button class=\"search-hero-clear\" (click)=\"entityFilterText = ''\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n } @else {\n <span class=\"search-hero-shortcut\">/</span>\n }\n </div>\n\n <!-- Meta row: entity count + All/Favorites pills -->\n <div class=\"search-meta-row\">\n <span class=\"search-entity-count\">\n {{ filteredEntityCount }} record types\n @if (applicationCount > 0) {\n across {{ applicationCount }} apps\n }\n </span>\n <div class=\"pill-toggle\">\n <button\n class=\"pill-btn\"\n [class.active]=\"state.homeViewMode === 'all'\"\n (click)=\"setHomeViewMode('all')\">\n All Data\n </button>\n <button\n class=\"pill-btn\"\n [class.active]=\"state.homeViewMode === 'favorites'\"\n (click)=\"setHomeViewMode('favorites')\">\n <i class=\"fa-solid fa-star\"></i> My Favorites\n </button>\n </div>\n </div>\n </div>\n\n <!-- Entity Groups Area -->\n <div class=\"entity-groups-area\">\n @if (entityFilter?.applicationId) {\n <!-- Single-app mode: flat entity grid (no grouping) -->\n <div class=\"entity-item-grid\">\n @for (entity of flatFilteredEntities; track entity.ID) {\n <div\n class=\"entity-item\"\n (click)=\"onEntitySelected(entity)\"\n [title]=\"entity.Description || entity.DisplayNameOrName\">\n <div class=\"entity-item-icon\">\n <i [class]=\"getEntityIcon(entity)\"></i>\n </div>\n <div class=\"entity-item-text\">\n <span class=\"entity-item-name\">{{ entity.DisplayNameOrName }}</span>\n @if (entity.Description) {\n <span class=\"entity-item-desc\">{{ entity.Description }}</span>\n }\n </div>\n <button\n class=\"entity-item-fav\"\n [class.favorited]=\"isEntityFavorited(entity)\"\n (click)=\"toggleEntityFavorite(entity, $event)\"\n [title]=\"isEntityFavorited(entity) ? 'Remove from favorites' : 'Add to favorites'\">\n <i [class]=\"isEntityFavorited(entity) ? 'fa-solid fa-star' : 'fa-regular fa-star'\"></i>\n </button>\n </div>\n }\n </div>\n } @else {\n <!-- Multi-app mode: grouped by application -->\n @for (group of filteredAppEntityGroups; track group.applicationId) {\n <div class=\"app-group\">\n <div class=\"app-group-header\" (click)=\"toggleAppGroup(group.applicationId)\">\n <div\n class=\"app-group-icon\"\n [style.background]=\"group.applicationColor ? group.applicationColor + '15' : null\"\n [style.color]=\"group.applicationColor || null\">\n <i [class]=\"group.applicationIcon || 'fa-solid fa-folder'\"></i>\n </div>\n <span class=\"app-group-name\">{{ group.applicationName }}</span>\n <span class=\"app-group-count\">{{ group.entities.length }}</span>\n <i class=\"fa-solid fa-chevron-right app-group-chevron\"\n [class.expanded]=\"group.isExpanded\"></i>\n </div>\n @if (group.isExpanded) {\n <div class=\"app-group-entities\">\n <div class=\"entity-item-grid\">\n @for (entity of group.entities; track entity.ID) {\n <div\n class=\"entity-item\"\n (click)=\"onEntitySelected(entity)\"\n [title]=\"entity.Description || entity.DisplayNameOrName\">\n <div class=\"entity-item-icon\">\n <i [class]=\"getEntityIcon(entity)\"></i>\n </div>\n <div class=\"entity-item-text\">\n <span class=\"entity-item-name\">{{ entity.DisplayNameOrName }}</span>\n @if (entity.Description) {\n <span class=\"entity-item-desc\">{{ entity.Description }}</span>\n }\n </div>\n <button\n class=\"entity-item-fav\"\n [class.favorited]=\"isEntityFavorited(entity)\"\n (click)=\"toggleEntityFavorite(entity, $event)\"\n [title]=\"isEntityFavorited(entity) ? 'Remove from favorites' : 'Add to favorites'\">\n <i [class]=\"isEntityFavorited(entity) ? 'fa-solid fa-star' : 'fa-regular fa-star'\"></i>\n </button>\n </div>\n }\n </div>\n </div>\n }\n </div>\n }\n }\n\n <!-- No results -->\n @if (filteredEntityCount === 0 && entities.length > 0) {\n <mj-empty-state Variant=\"no-results\"\n Title=\"No matches found\"\n [Message]=\"NoEntityResultsMessage\" />\n }\n @if (entities.length === 0) {\n <mj-empty-state Icon=\"fa-solid fa-database\"\n Title=\"Nothing here yet\"\n Message=\"This app doesn't have any data set up yet. An administrator can add data for you to explore here.\" />\n }\n </div>\n </div>\n\n <!-- Quick Access Panel (right slide-in) -->\n <div class=\"quick-access-panel\" [class.open]=\"state.quickAccessPanelOpen\">\n <div class=\"qa-header\">\n <h3>Quick Access</h3>\n <button class=\"qa-close-btn\" (click)=\"toggleQuickAccessPanel()\">\n <i class=\"fa-solid fa-xmark\"></i>\n </button>\n </div>\n <div class=\"qa-body\">\n <!-- Recent Records (max 3) -->\n <mj-accordion-panel [Expanded]=\"isQuickAccessSectionExpanded('recentRecords')\" (ExpandedChange)=\"toggleQuickAccessSection('recentRecords')\">\n <ng-template mjAccordionTitle>\n <i class=\"fa-solid fa-clock-rotate-left qa-section-icon\"></i>\n <span>Recent Records</span>\n <span class=\"mj-accordion-badge mj-accordion-badge--right\">{{ quickAccessRecentRecords.length }}</span>\n </ng-template>\n @for (record of quickAccessRecentRecords; track record.entityId + '|' + record.recordId) {\n <div class=\"qa-item\" (click)=\"onRecentRecordClick(record)\">\n <div class=\"qa-item-icon\">\n <i [class]=\"getEntityIconById(record.entityId)\"></i>\n </div>\n <div class=\"qa-item-info\">\n <div class=\"qa-item-name\">{{ record.recordName || record.recordId }}</div>\n <div class=\"qa-item-meta\">{{ getEntityDisplayName(record.entityName) }}</div>\n </div>\n <span class=\"qa-item-time\">{{ record.relativeTime }}</span>\n </div>\n }\n @if (quickAccessRecentRecords.length === 0) {\n <div class=\"qa-empty\">No recent records</div>\n }\n </mj-accordion-panel>\n\n <!-- Recent Entities (max 3) -->\n <mj-accordion-panel [Expanded]=\"isQuickAccessSectionExpanded('recentEntities')\" (ExpandedChange)=\"toggleQuickAccessSection('recentEntities')\">\n <ng-template mjAccordionTitle>\n <i class=\"fa-solid fa-table qa-section-icon\"></i>\n <span>Recent Record Types</span>\n <span class=\"mj-accordion-badge mj-accordion-badge--right\">{{ quickAccessRecentEntities.length }}</span>\n </ng-template>\n @for (entity of quickAccessRecentEntities; track entity.ID) {\n <div class=\"qa-item\" (click)=\"onEntitySelected(entity)\">\n <div class=\"qa-item-icon\">\n <i [class]=\"getEntityIcon(entity)\"></i>\n </div>\n <div class=\"qa-item-info\">\n <div class=\"qa-item-name\">{{ entity.DisplayNameOrName }}</div>\n </div>\n </div>\n }\n @if (quickAccessRecentEntities.length === 0) {\n <div class=\"qa-empty\">No recent record types</div>\n }\n </mj-accordion-panel>\n\n <!-- Favorite Records (max 3) -->\n <mj-accordion-panel [Expanded]=\"isQuickAccessSectionExpanded('favoriteRecords')\" (ExpandedChange)=\"toggleQuickAccessSection('favoriteRecords')\">\n <ng-template mjAccordionTitle>\n <i class=\"fa-solid fa-star qa-section-icon\" style=\"color: var(--mj-status-warning);\"></i>\n <span>Favorite Records</span>\n <span class=\"mj-accordion-badge mj-accordion-badge--right\">{{ quickAccessFavoriteRecords.length }}</span>\n </ng-template>\n @for (record of quickAccessFavoriteRecords; track record.userFavoriteId) {\n <div class=\"qa-item\" (click)=\"onFavoriteRecordClick(record)\">\n <div class=\"qa-item-icon\">\n <i [class]=\"getEntityIconById(record.entityId)\"></i>\n </div>\n <div class=\"qa-item-info\">\n <div class=\"qa-item-name\">{{ record.recordName || record.recordId }}</div>\n <div class=\"qa-item-meta\">{{ getEntityDisplayName(record.entityName) }}</div>\n </div>\n </div>\n }\n @if (quickAccessFavoriteRecords.length === 0) {\n <div class=\"qa-empty\">No favorite records</div>\n }\n </mj-accordion-panel>\n </div>\n </div>\n }\n </div>\n } @else {\n <!--\n mj-view-workspace owns the saved-view lifecycle (select / save / duplicate / rename /\n delete / revert / quick-save) and the inner data renderer. Every view type is now a\n self-contained plug-in: view-type-specific features (export / add-to-list / delete /\n grid-state / timeline + map controls) live INSIDE the plug-in renderers and no longer\n bubble through the workspace. The dashboard only handles navigation (open record / open\n related record / create new record) + the generic filter/data-load/count signals + URL\n and view-CRUD state sync. AutoSaveView=true \u2192 the workspace persists view CRUD itself.\n -->\n <mj-view-workspace\n [Entity]=\"selectedEntity\"\n [AutoSaveView]=\"true\"\n [SelectedView]=\"selectedViewEntity\"\n [FilterText]=\"debouncedFilterText\"\n [SelectedRecordId]=\"state.selectedRecordId\"\n [ViewerConfig]=\"viewerConfig\"\n (SelectedViewChange)=\"onWorkspaceViewSelected($event)\"\n (ViewSelected)=\"onWorkspaceViewSelected($event)\"\n (OpenViewInTabRequested)=\"onOpenInTabRequested($event)\"\n (OpenRecordRequested)=\"onWorkspaceOpenRecord($event)\"\n (OpenRelatedRecordRequested)=\"onOpenRelatedRecordRequested($event)\"\n (CreateNewRecordRequested)=\"onCreateNewRecordRequested($event)\"\n (RecordSelected)=\"onViewerRecordSelected($event)\"\n (AfterViewSave)=\"onWorkspaceViewSaved()\"\n (AfterViewDelete)=\"onWorkspaceViewSaved()\"\n (FilterTextChanged)=\"onFilterTextChanged($event)\"\n (DataLoaded)=\"onDataLoaded($event)\"\n (FilteredCountChanged)=\"onFilteredCountChanged($event)\">\n </mj-view-workspace>\n }\n </div>\n </div>\n\n <!-- Detail Panel (Right - Slide In) -->\n @if (state.detailPanelOpen && selectedRecord) {\n <div class=\"detail-panel\" [style.width.px]=\"state.detailPanelWidth\">\n <mj-entity-record-detail-panel\n [entity]=\"detailPanelEntity\"\n [record]=\"selectedRecord\"\n (close)=\"onDetailPanelClosed()\"\n (openRecord)=\"onOpenRecord($event)\"\n (navigateToRelated)=\"onNavigateToRelated($event)\"\n (openRelatedRecord)=\"onOpenRelatedRecord($event)\"\n (openForeignKeyRecord)=\"onOpenForeignKeyRecord($event)\">\n </mj-entity-record-detail-panel>\n </div>\n }\n\n <!-- View config panel, filter dialog, quick-save/duplicate/shared-view-warning dialogs\n now live inside mj-view-workspace (the saved-view lifecycle moved there).\n Export + Add-to-List dialogs now live inside the grid plug-in renderer (it hosts its own\n Generic dialogs), so they are no longer rendered at the dashboard level. -->\n</div>\n", styles: [".data-explorer-container {\n display: flex;\n height: 100%;\n width: 100%;\n background: var(--mj-bg-surface-card);\n overflow: hidden;\n}\n\n.navigation-panel {\n flex-shrink: 0;\n height: 100%;\n background: var(--mj-bg-surface);\n border-right: 1px solid var(--mj-border-default);\n transition: width 0.2s ease-in-out;\n overflow: hidden;\n box-shadow: 2px 0 8px rgba(0, 0, 0, 0.04);\n}\n.navigation-panel.collapsed {\n width: 48px;\n}\n\n.content-area {\n flex: 1;\n display: flex;\n flex-direction: column;\n height: 100%;\n min-width: 0;\n overflow: hidden;\n background: var(--mj-bg-surface-card);\n}\n\n/* Breadcrumb Bar */\n.breadcrumb-bar {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 24px;\n background: var(--mj-bg-surface);\n border-bottom: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n font-size: 13px;\n min-height: 40px;\n}\n\n.breadcrumb-item {\n display: flex;\n align-items: center;\n gap: 6px;\n color: var(--mj-text-secondary);\n padding: 4px 8px;\n border-radius: 4px;\n transition: all 0.15s ease;\n max-width: 200px;\n}\n\n.breadcrumb-item.clickable {\n cursor: pointer;\n}\n\n.breadcrumb-item.clickable:hover {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-brand-primary);\n}\n\n.breadcrumb-item.current {\n color: var(--mj-text-primary);\n font-weight: 500;\n cursor: default;\n}\n\n.breadcrumb-icon {\n font-size: 12px;\n flex-shrink: 0;\n}\n\n.breadcrumb-label {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.breadcrumb-separator {\n font-size: 10px;\n color: var(--mj-border-strong);\n flex-shrink: 0;\n}\n\n.content-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px 24px;\n background: var(--mj-bg-surface);\n border-bottom: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n gap: 24px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);\n position: relative;\n z-index: 2; /* Above sibling .content-body so header dropdowns render on top of grid/map content */\n}\n\n.header-left {\n display: flex;\n align-items: center;\n gap: 12px;\n flex-shrink: 0;\n flex-wrap: wrap;\n}\n\n/* View Selector within header */\n.header-left ::ng-deep mj-view-selector {\n margin-left: 8px;\n}\n\n.entity-icon {\n font-size: 20px;\n color: var(--mj-brand-primary);\n}\n\n.entity-title {\n margin: 0;\n font-size: 20px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.record-count {\n font-size: 13px;\n color: var(--mj-text-secondary);\n font-weight: 500;\n}\n\n.header-center {\n flex: 1;\n max-width: 600px;\n}\n\n.smart-filter-container {\n position: relative;\n width: 100%;\n}\n\n.smart-filter-input {\n width: 100%;\n padding: 10px 40px 10px 16px;\n border: 1px solid var(--mj-border-strong);\n border-radius: 8px;\n font-size: 14px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n transition: all 0.15s ease;\n}\n.smart-filter-input:focus {\n outline: none;\n border-color: var(--mj-brand-primary);\n background: var(--mj-bg-surface);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n}\n.smart-filter-input::placeholder {\n color: var(--mj-text-muted);\n}\n\n.clear-filter-btn {\n position: absolute;\n right: 8px;\n top: 50%;\n transform: translateY(-50%);\n width: 28px;\n height: 28px;\n border: none;\n background: transparent;\n border-radius: 50%;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-muted);\n transition: all 0.15s ease;\n}\n.clear-filter-btn:hover {\n background: var(--mj-border-default);\n color: var(--mj-text-primary);\n}\n\n.header-right {\n display: flex;\n align-items: center;\n gap: 16px;\n flex-shrink: 0;\n}\n\n.view-mode-toggle {\n display: flex;\n background: var(--mj-bg-surface-sunken);\n border-radius: 8px;\n padding: 3px;\n}\n\n.toggle-btn {\n width: 36px;\n height: 32px;\n border: none;\n background: transparent;\n border-radius: 6px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-secondary);\n transition: all 0.15s ease;\n}\n.toggle-btn:hover {\n color: var(--mj-text-primary);\n}\n.toggle-btn.active {\n background: var(--mj-bg-surface);\n color: var(--mj-brand-primary);\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n}\n\n/* Header Action Button (Add to List, etc.) */\n.header-action-btn {\n position: relative;\n width: 36px;\n height: 36px;\n border: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n border-radius: 8px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-secondary);\n transition: all 0.15s ease;\n margin-left: 8px;\n}\n.header-action-btn:hover:not(.disabled) {\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 5%, var(--mj-bg-surface));\n}\n.header-action-btn.disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n.header-action-btn i {\n font-size: 14px;\n}\n.selection-badge {\n position: absolute;\n top: -6px;\n right: -6px;\n min-width: 18px;\n height: 18px;\n padding: 0 5px;\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n font-size: 11px;\n font-weight: 600;\n border-radius: 9px;\n display: flex;\n align-items: center;\n justify-content: center;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);\n}\n\n/* Timeline Date Field Selector - Styled Dropdown */\n.date-field-selector-container {\n display: flex;\n align-items: center;\n position: relative;\n}\n\n.date-field-backdrop {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n z-index: 999;\n background: transparent;\n}\n\n.date-field-selector-wrapper {\n position: relative;\n}\n\n.date-field-selector-button {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 6px 12px;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n cursor: pointer;\n font-size: 13px;\n color: var(--mj-text-primary);\n transition: all 0.15s ease;\n min-width: 120px;\n max-width: 200px;\n}\n\n.date-field-selector-button:hover:not(:disabled) {\n background: var(--mj-bg-surface-hover);\n border-color: var(--mj-border-strong);\n}\n\n.date-field-selector-button:disabled {\n cursor: default;\n opacity: 0.8;\n}\n\n.date-field-selector-button.open {\n background: var(--mj-bg-surface-active);\n border-color: var(--mj-brand-primary);\n}\n\n.date-field-icon {\n color: var(--mj-text-secondary);\n font-size: 12px;\n}\n\n.date-field-name {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n text-align: left;\n}\n\n.date-field-arrow {\n color: var(--mj-text-secondary);\n font-size: 10px;\n transition: transform 0.2s ease;\n}\n\n.date-field-arrow.rotated {\n transform: rotate(180deg);\n}\n\n.date-field-dropdown-panel {\n position: absolute;\n top: calc(100% + 4px);\n left: 0;\n min-width: 180px;\n max-width: 280px;\n max-height: 300px;\n overflow-y: auto;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);\n z-index: 1000;\n}\n\n.date-field-dropdown-item {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 10px 14px;\n cursor: pointer;\n transition: background 0.1s ease;\n font-size: 13px;\n}\n\n.date-field-dropdown-item:hover {\n background: var(--mj-bg-surface-card);\n}\n\n.date-field-dropdown-item.selected {\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n}\n\n.date-field-dropdown-item.selected:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n}\n\n.date-field-dropdown-item .item-icon {\n color: var(--mj-text-secondary);\n font-size: 13px;\n width: 16px;\n text-align: center;\n}\n\n.date-field-dropdown-item .item-name {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.date-field-dropdown-item .selected-check {\n color: var(--mj-brand-primary);\n font-size: 12px;\n}\n\n/* View-specific controls \u2014 contextual controls for the active view mode.\n Positioned LEFT of the view mode toggle with a subtle visual separator. */\n.view-specific-controls {\n display: flex;\n align-items: center;\n gap: 4px;\n padding: 3px 8px;\n background: var(--mj-bg-surface-sunken);\n border-radius: 8px;\n border: 1px solid var(--mj-border-subtle);\n}\n\n.view-specific-btn {\n width: 30px;\n height: 30px;\n border: none;\n background: transparent;\n border-radius: 6px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-muted);\n transition: all 0.15s ease;\n}\n\n.view-specific-btn:hover {\n color: var(--mj-text-secondary);\n background: var(--mj-bg-surface-hover);\n}\n\n/* Legacy \u2014 keep for backward compat but no longer used in template */\n.timeline-orientation-toggle {\n display: flex;\n background: var(--mj-bg-surface-sunken);\n border-radius: 8px;\n padding: 3px;\n}\n\n.content-body {\n display: flex;\n flex: 1 1 0%;\n overflow: hidden;\n /* Contain the body's stacking context. The view components inside (grid\n option menus, the map's Leaflet panes/toolbar, etc.) use z-index values up\n to ~1000; .content-header sits at z-index 2 (kept below the shell header \u2014\n see #2701). Without this, those body z-indices leak into the shared context\n and paint over the header's own dropdowns (view-selector, date-field).\n `isolation: isolate` flattens all body z-indices into one context beneath\n the header. Safe because modals + the detail panel render at the dashboard\n root (outside .content-body), so they still overlay everything. */\n isolation: isolate;\n position: relative; /* added */\n z-index: 3; /* added \u2014 higher than .content-header's z-index: 2 */\n}\n\n/* ============================================\n DASHBOARDS MOVED BANNER\n ============================================ */\n\n/* Sits between .content-header and .content-body in the .content-area column. Side margins line up with the header text. */\n.dx-moved-banner {\n display: flex;\n flex-wrap: wrap;\n flex-shrink: 0;\n align-items: center;\n gap: 10px;\n padding: 10px 14px;\n margin: 12px 24px;\n border-radius: 10px;\n background: var(--mj-status-info-bg);\n color: var(--mj-status-info-text);\n font-size: 13px;\n transition: margin-right 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n/* The home header has no bottom border and ends with its own padding. */\n.content-header.home-header + .dx-moved-banner {\n margin-top: 0;\n}\n\n/* The open Quick Access panel covers the right 320px, so the banner ends 24px before it. */\n.dx-moved-banner.panel-open {\n margin-right: 344px;\n}\n\n/* The text fills the free space. When less than 200px is free, the buttons go to a second line. */\n.dx-moved-banner span {\n flex: 1 1 200px;\n}\n\n.loading-container,\n.loading-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n gap: 16px;\n background: var(--mj-bg-surface);\n border-radius: 8px;\n}\n\n.loading-spinner {\n font-size: 32px;\n color: var(--mj-brand-primary);\n}\n\n.loading-message {\n margin: 0;\n font-size: 14px;\n color: var(--mj-text-secondary);\n}\n\n\n/* ============================================\n CONCEPT D: HOME VIEW LAYOUT\n ============================================ */\n\n.home-view-concept-d {\n flex: 1;\n display: flex;\n overflow: hidden;\n}\n\n.home-main-area {\n flex: 1;\n overflow-y: auto;\n transition: margin-right 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.home-main-area.panel-open {\n margin-right: 320px;\n}\n\n/* ============================================\n SEARCH HERO\n ============================================ */\n\n.search-hero {\n background: var(--mj-bg-surface);\n padding: 36px 40px 24px;\n text-align: center;\n}\n\n.search-hero-container {\n position: relative;\n max-width: 600px;\n margin: 0 auto;\n}\n\n.search-hero-input {\n width: 100%;\n padding: 14px 18px 14px 44px;\n font-size: 15px;\n border: 2px solid var(--mj-border-default);\n border-radius: 12px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n outline: none;\n transition: all 0.2s;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06);\n font-family: inherit;\n}\n\n.search-hero-input:focus {\n border-color: var(--mj-brand-primary);\n box-shadow: 0 12px 40px rgba(0, 0, 0, 0.1), 0 0 0 4px color-mix(in srgb, var(--mj-brand-primary) 8%, transparent);\n}\n\n.search-hero-input::placeholder {\n color: var(--mj-text-primary);\n}\n\n.search-hero-icon {\n position: absolute;\n left: 16px;\n top: 50%;\n transform: translateY(-50%);\n color: var(--mj-text-primary);\n font-size: 15px;\n pointer-events: none;\n}\n\n.search-hero-shortcut {\n position: absolute;\n right: 14px;\n top: 50%;\n transform: translateY(-50%);\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n padding: 2px 7px;\n border-radius: 5px;\n font-size: 12px;\n color: var(--mj-text-muted);\n}\n\n.search-hero-clear {\n position: absolute;\n right: 14px;\n top: 50%;\n transform: translateY(-50%);\n width: 28px;\n height: 28px;\n border: none;\n background: transparent;\n border-radius: 50%;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-muted);\n transition: all 0.15s ease;\n}\n\n.search-hero-clear:hover {\n background: var(--mj-border-default);\n color: var(--mj-text-primary);\n}\n\n/* Meta row: entity count + pills */\n.search-meta-row {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 12px;\n margin-top: 16px;\n}\n\n.search-entity-count {\n font-size: 13px;\n color: var(--mj-text-muted);\n}\n\n.pill-toggle {\n display: flex;\n gap: 6px;\n}\n\n.pill-btn {\n padding: 6px 16px;\n border-radius: 20px;\n border: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n font-size: 13px;\n color: var(--mj-text-secondary);\n cursor: pointer;\n transition: all 0.15s;\n font-family: inherit;\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n.pill-btn:hover {\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-primary);\n}\n\n.pill-btn.active {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n border-color: var(--mj-brand-primary);\n}\n\n.pill-btn i {\n font-size: 11px;\n}\n\n/* ============================================\n APPLICATION GROUPS\n ============================================ */\n\n.entity-groups-area {\n padding: 12px 40px 60px;\n max-width: 1200px;\n margin: 0 auto;\n}\n\n.app-group {\n margin-bottom: 4px;\n}\n\n.app-group-header {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 12px 14px;\n cursor: pointer;\n border-radius: 8px;\n transition: background 0.12s;\n user-select: none;\n}\n\n.app-group-header:hover {\n background: rgba(0, 0, 0, 0.025);\n}\n\n.app-group-icon {\n width: 38px;\n height: 38px;\n border-radius: 10px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 17px;\n flex-shrink: 0;\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n}\n\n.app-group-name {\n font-size: 21px;\n font-weight: 600;\n flex: 1;\n}\n\n.app-group-count {\n font-size: 12px;\n color: var(--mj-text-muted);\n background: var(--mj-bg-surface-card);\n padding: 2px 9px;\n border-radius: 10px;\n}\n\n.app-group-chevron {\n color: var(--mj-text-muted);\n font-size: 12px;\n transition: transform 0.2s;\n}\n\n.app-group-chevron.expanded {\n transform: rotate(90deg);\n}\n\n.app-group-entities {\n padding: 4px 14px 10px 60px;\n}\n\n/* ============================================\n ENTITY ITEM GRID (pills)\n ============================================ */\n\n.entity-item-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));\n gap: 12px;\n}\n\n.entity-item {\n display: flex;\n align-items: flex-start;\n gap: 12px;\n padding: 10px 14px;\n border-radius: 8px;\n cursor: pointer;\n transition: all 0.12s;\n border: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n}\n\n.entity-item:hover {\n border-color: color-mix(in srgb, var(--mj-brand-primary) 40%, var(--mj-bg-surface));\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);\n background: color-mix(in srgb, var(--mj-brand-primary) 5%, var(--mj-bg-surface));\n}\n\n.entity-item-icon {\n width: 32px;\n height: 32px;\n border-radius: 8px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 14px;\n color: var(--mj-text-secondary);\n background: var(--mj-bg-surface-card);\n flex-shrink: 0;\n}\n\n.entity-item:hover .entity-item-icon {\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n}\n\n.entity-item-text {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.entity-item-name {\n font-size: 14px;\n font-weight: 500;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.entity-item-desc {\n font-size: 12px;\n color: var(--mj-text-muted);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n line-height: 1.3;\n}\n\n.entity-item-fav {\n color: var(--mj-text-muted);\n opacity: 0;\n transition: opacity 0.12s;\n font-size: 11px;\n cursor: pointer;\n flex-shrink: 0;\n background: none;\n border: none;\n padding: 4px;\n align-self: center;\n}\n\n.entity-item:hover .entity-item-fav {\n opacity: 1;\n}\n\n.entity-item-fav.favorited {\n opacity: 1;\n color: var(--mj-status-warning);\n}\n\n/* ============================================\n QUICK ACCESS PANEL (right slide-in)\n ============================================ */\n\n.quick-access-panel {\n position: fixed;\n top: 60px;\n right: 0;\n width: 320px;\n height: calc(100% - 60px);\n background: var(--mj-bg-surface);\n border-left: 1px solid var(--mj-border-default);\n z-index: 50;\n transform: translateX(100%);\n transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n display: flex;\n flex-direction: column;\n}\n\n.quick-access-panel.open {\n transform: translateX(0);\n}\n\n.qa-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 12px 16px;\n border-bottom: 1px solid var(--mj-border-default);\n}\n\n.qa-header h3 {\n font-size: 14px;\n font-weight: 600;\n margin: 0;\n}\n\n.qa-close-btn {\n background: none;\n border: none;\n color: var(--mj-text-muted);\n cursor: pointer;\n font-size: 14px;\n padding: 4px;\n border-radius: 4px;\n}\n\n.qa-close-btn:hover {\n background: var(--mj-bg-surface-card);\n}\n\n.qa-body {\n flex: 1;\n overflow-y: auto;\n}\n\n.qa-item {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 8px;\n border-radius: 8px;\n cursor: pointer;\n transition: background 0.08s;\n}\n\n.qa-item:hover {\n background: var(--mj-bg-surface-card);\n}\n\n.qa-item-icon {\n width: 28px;\n height: 28px;\n border-radius: 6px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 12px;\n background: var(--mj-bg-surface-card);\n color: var(--mj-text-secondary);\n flex-shrink: 0;\n}\n\n.qa-item-info {\n flex: 1;\n min-width: 0;\n}\n\n.qa-item-name {\n font-size: 13px;\n font-weight: 500;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.qa-item-meta {\n font-size: 11px;\n color: var(--mj-text-muted);\n}\n\n.qa-item-time {\n font-size: 11px;\n color: var(--mj-text-muted);\n white-space: nowrap;\n}\n\n.qa-empty {\n padding: 20px 16px;\n text-align: center;\n color: var(--mj-text-muted);\n font-size: 13px;\n}\n\n/* Quick Access toggle button badge */\n.qa-badge {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n font-size: 10px;\n font-weight: 600;\n padding: 0 5px;\n border-radius: 8px;\n min-width: 16px;\n text-align: center;\n}\n\n.detail-panel {\n flex-shrink: 0;\n height: 100%;\n background: var(--mj-bg-surface);\n border-left: 1px solid var(--mj-border-default);\n box-shadow: -4px 0 16px rgba(0, 0, 0, 0.08);\n overflow: hidden;\n animation: slideIn 0.2s ease-out;\n display: flex;\n flex-direction: column;\n}\n\n.detail-panel mj-entity-record-detail-panel {\n flex: 1;\n min-height: 0;\n overflow: hidden;\n}\n\n@keyframes slideIn {\n from {\n transform: translateX(100%);\n opacity: 0;\n }\n to {\n transform: translateX(0);\n opacity: 1;\n }\n}\n\n/* Detail Panel Actions Bar */\n.detail-panel-actions {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n background: var(--mj-bg-surface-card);\n border-bottom: 1px solid var(--mj-border-default);\n}\n\n.detail-action-btn {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 6px 12px;\n border: none;\n border-radius: 6px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);\n}\n\n.detail-action-btn:hover {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n transform: translateY(-1px);\n box-shadow: 0 2px 4px color-mix(in srgb, var(--mj-brand-primary) 20%, transparent);\n}\n\n.detail-action-btn i {\n font-size: 12px;\n}\n\n:host ::ng-deep mj-explorer-grid-view,\n:host ::ng-deep mj-explorer-cards-view {\n flex: 1;\n display: flex;\n flex-direction: column;\n min-height: 0;\n}\n\n/* ============================================\n HOME SCREEN STYLES\n ============================================ */\n\n/* Home-level header adjustments */\n.content-header.home-header {\n border-bottom: none;\n background: transparent;\n box-shadow: none;\n padding: 16px 24px 8px;\n}\n\n.content-body.home-content {\n padding: 0;\n}\n\n/* Smart filter with search icon (entity-level filter) */\n.filter-icon {\n position: absolute;\n left: 14px;\n top: 50%;\n transform: translateY(-50%);\n color: var(--mj-text-muted);\n font-size: 14px;\n pointer-events: none;\n}\n\n.smart-filter-container .smart-filter-input {\n padding-left: 40px;\n}\n\n/* ============================================\n VIEW MODE TOGGLE OVERRIDES\n ============================================ */\n\n.view-mode-toggle .toggle-btn {\n width: auto;\n padding: 0 12px;\n gap: 6px;\n}\n\n.toggle-label {\n font-size: 12px;\n font-weight: 500;\n}\n\n/* ============================================\n RESPONSIVE STYLES\n ============================================ */\n\n@media (max-width: 1200px) {\n .entity-item-grid {\n grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));\n }\n}\n\n@media (max-width: 900px) {\n .content-header {\n flex-wrap: wrap;\n gap: 12px;\n }\n\n .header-center {\n order: 3;\n flex-basis: 100%;\n max-width: 100%;\n }\n\n .search-hero {\n padding: 24px 16px 16px;\n }\n\n .entity-groups-area {\n padding: 8px 16px 40px;\n }\n\n .app-group-entities {\n padding-left: 36px;\n }\n\n .home-main-area.panel-open {\n margin-right: 0;\n }\n\n .quick-access-panel {\n width: 100%;\n }\n\n /* The Quick Access panel covers the full width here, so the banner keeps its normal margin. */\n .dx-moved-banner.panel-open {\n margin-right: 24px;\n }\n}\n\n@media (max-width: 600px) {\n .data-explorer-container {\n flex-direction: column;\n }\n\n .navigation-panel {\n display: none;\n }\n\n .content-header {\n padding: 12px 16px;\n }\n\n .content-body {\n padding: 12px 16px;\n }\n\n /* The entity header has 16px side padding here. The home header keeps 24px. */\n .content-header:not(.home-header) + .dx-moved-banner {\n margin-left: 16px;\n margin-right: 16px;\n }\n\n .entity-item-grid {\n grid-template-columns: 1fr;\n }\n\n .view-mode-toggle {\n display: none;\n }\n\n .app-group-name {\n font-size: 17px;\n }\n\n .entity-item-name {\n font-size: 14px;\n }\n}\n"] }]
|
|
3630
|
+
], template: "<div class=\"data-explorer-container\">\n <!-- Navigation Panel (Left) - Hidden at home level, animated -->\n @if (!isAtHomeLevel) {\n <div\n class=\"navigation-panel\"\n [class.collapsed]=\"state.navigationPanelCollapsed\"\n [style.width.px]=\"state.navigationPanelCollapsed ? 48 : state.navigationPanelWidth\"\n [@slideInLeft]>\n\n <mj-explorer-navigation-panel\n [entities]=\"entities\"\n [appEntityGroups]=\"appEntityGroups\"\n [selectedEntityName]=\"state.selectedEntityName\"\n [favorites]=\"state.favorites\"\n [recentItems]=\"state.recentItems\"\n [collapsed]=\"state.navigationPanelCollapsed\"\n [allowedEntityNames]=\"allowedEntityNames\"\n [favoritesSectionExpanded]=\"state.favoritesSectionExpanded\"\n [recentSectionExpanded]=\"state.recentSectionExpanded\"\n [entitiesSectionExpanded]=\"state.entitiesSectionExpanded\"\n [viewsSectionExpanded]=\"state.viewsSectionExpanded\"\n (entitySelected)=\"onEntitySelected($event)\"\n (toggleCollapse)=\"toggleNavigationPanel()\"\n (sectionToggled)=\"stateService.toggleSection($event)\"\n (openRecord)=\"onOpenRecordFromNav($event)\"\n (selectRecord)=\"onSelectRecordFromNav($event)\"\n (expandAndFocus)=\"onExpandAndFocus($event)\">\n </mj-explorer-navigation-panel>\n </div>\n }\n\n <!-- Main Content Area -->\n <div class=\"content-area\">\n <!-- Breadcrumb Bar - Hidden at home level -->\n @if (!isAtHomeLevel && breadcrumbs.length > 0) {\n <div class=\"breadcrumb-bar\">\n @for (crumb of breadcrumbs; track crumb.label; let i = $index; let last = $last) {\n <span\n class=\"breadcrumb-item\"\n [class.clickable]=\"!last\"\n [class.current]=\"last\"\n (click)=\"onBreadcrumbClick(crumb, i)\"\n [title]=\"crumb.label\">\n @if (crumb.icon) {\n <i [class]=\"crumb.icon\" class=\"breadcrumb-icon\"></i>\n }\n <span class=\"breadcrumb-label\">{{ crumb.label }}</span>\n </span>\n @if (!last) {\n <i class=\"fa-solid fa-chevron-right breadcrumb-separator\"></i>\n }\n }\n </div>\n }\n\n <!-- Header -->\n <div class=\"content-header\" [class.home-header]=\"isAtHomeLevel\">\n <div class=\"header-left\">\n @if (selectedEntity) {\n <i [class]=\"getEntityIcon(selectedEntity)\" class=\"entity-icon\"></i>\n <h2 class=\"entity-title\">{{ selectedEntity.DisplayNameOrName }}</h2>\n @if (debouncedFilterText && filteredRecordCount !== totalRecordCount) {\n <span class=\"record-count\">{{ filteredRecordCount | number }} of {{ totalRecordCount | number }} records</span>\n } @else {\n <span class=\"record-count\">{{ totalRecordCount | number }} records</span>\n }\n\n <!-- View selector + view header now live inside mj-view-workspace (below). -->\n <!-- Add-to-List is now owned inside the grid plug-in renderer (it hosts its own dialog). -->\n } @else {\n @if (displayIcon) {\n <i [class]=\"displayIcon\" class=\"entity-icon\"></i>\n }\n <h2 class=\"entity-title\">{{ displayTitle }}</h2>\n <span class=\"record-count\">{{ entities.length }} record types available</span>\n }\n </div>\n\n <div class=\"header-center\">\n @if (selectedEntity) {\n <div class=\"smart-filter-container\">\n <i class=\"fa-solid fa-search filter-icon\"></i>\n <input\n #filterInput\n type=\"text\"\n class=\"smart-filter-input\"\n placeholder=\"Filter records... (press / to focus)\"\n [value]=\"liveFilterText\"\n (input)=\"onFilterInputChanged(filterInput.value)\"\n />\n @if (liveFilterText) {\n <button class=\"clear-filter-btn\" (click)=\"clearRecordFilter()\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n }\n </div>\n }\n <!-- Home-level search moved to search hero section -->\n </div>\n\n <div class=\"header-right\">\n @if (selectedEntity) {\n <!-- View-type switcher AND all view-specific controls (timeline date/orientation/sort,\n map render mode, etc.) now live inside the plug-in renderers hosted by\n mj-view-workspace. Nothing view-type-specific lives in this header anymore. -->\n }\n @if (!selectedEntity) {\n <!-- Quick Access panel toggle -->\n <button\n class=\"header-action-btn\"\n [class.active]=\"state.quickAccessPanelOpen\"\n (click)=\"toggleQuickAccessPanel()\"\n title=\"Recents & Favorites\">\n <i class=\"fa-solid fa-clock-rotate-left\"></i>\n @if (recentRecords.length + favoriteRecords.length > 0) {\n <span class=\"qa-badge\">{{ recentRecords.length + favoriteRecords.length }}</span>\n }\n </button>\n }\n </div>\n </div>\n\n <!-- Dashboards moved banner: shows with or without a selected entity.\n panel-open makes room for the Quick Access panel, which shows only on the loaded home view. -->\n @if (ShowDashboardsMovedBanner) {\n <div class=\"dx-moved-banner\" role=\"status\"\n [class.panel-open]=\"State.quickAccessPanelOpen && !SelectedEntity && !IsLoadingEntities\">\n <i class=\"fa-solid fa-gauge-high\" aria-hidden=\"true\"></i>\n <span><b>Dashboards moved.</b> They now live in the Dashboards app.</span>\n <button mjButton size=\"sm\" (click)=\"OpenDashboardsApp()\">Open Dashboards</button>\n <button mjButton size=\"sm\" variant=\"flat\" (click)=\"DismissDashboardsMovedBanner()\">Got it</button>\n </div>\n }\n\n <!-- Content Body - Using mj-entity-viewer composite -->\n <div class=\"content-body\" [class.home-content]=\"isAtHomeLevel\">\n @if (!selectedEntity) {\n <!-- Concept D: Application Groups + Search-First Home View -->\n <div class=\"home-view-concept-d\">\n @if (isLoadingEntities) {\n <div class=\"loading-state\">\n <mj-loading text=\"Loading your data...\" size=\"medium\"></mj-loading>\n </div>\n } @else {\n <div class=\"home-main-area\" [class.panel-open]=\"state.quickAccessPanelOpen\">\n <!-- Search Hero -->\n <div class=\"search-hero\">\n <div class=\"search-hero-container\">\n <i class=\"fa-solid fa-magnifying-glass search-hero-icon\"></i>\n <input\n #filterInput\n type=\"text\"\n class=\"search-hero-input\"\n placeholder=\"Search your data...\"\n [(ngModel)]=\"entityFilterText\"\n />\n @if (entityFilterText) {\n <button class=\"search-hero-clear\" (click)=\"entityFilterText = ''\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n } @else {\n <span class=\"search-hero-shortcut\">/</span>\n }\n </div>\n\n <!-- Meta row: entity count + All/Favorites pills -->\n <div class=\"search-meta-row\">\n <span class=\"search-entity-count\">\n {{ filteredEntityCount }} record types\n @if (applicationCount > 0) {\n across {{ applicationCount }} apps\n }\n </span>\n <div class=\"pill-toggle\">\n <button\n class=\"pill-btn\"\n [class.active]=\"state.homeViewMode === 'all'\"\n (click)=\"setHomeViewMode('all')\">\n All Data\n </button>\n <button\n class=\"pill-btn\"\n [class.active]=\"state.homeViewMode === 'favorites'\"\n (click)=\"setHomeViewMode('favorites')\">\n <i class=\"fa-solid fa-star\"></i> My Favorites\n </button>\n </div>\n </div>\n </div>\n\n <!-- Entity Groups Area -->\n <div class=\"entity-groups-area\">\n @if (entityFilter?.applicationId) {\n <!-- Single-app mode: flat entity grid (no grouping) -->\n <div class=\"entity-item-grid\">\n @for (entity of flatFilteredEntities; track entity.ID) {\n <div\n class=\"entity-item\"\n (click)=\"onEntitySelected(entity)\"\n [title]=\"entity.Description || entity.DisplayNameOrName\">\n <div class=\"entity-item-icon\">\n <i [class]=\"getEntityIcon(entity)\"></i>\n </div>\n <div class=\"entity-item-text\">\n <span class=\"entity-item-name\">{{ entity.DisplayNameOrName }}</span>\n @if (entity.Description) {\n <span class=\"entity-item-desc\">{{ entity.Description }}</span>\n }\n </div>\n <button\n class=\"entity-item-fav\"\n [class.favorited]=\"isEntityFavorited(entity)\"\n (click)=\"toggleEntityFavorite(entity, $event)\"\n [title]=\"isEntityFavorited(entity) ? 'Remove from favorites' : 'Add to favorites'\">\n <i [class]=\"isEntityFavorited(entity) ? 'fa-solid fa-star' : 'fa-regular fa-star'\"></i>\n </button>\n </div>\n }\n </div>\n } @else {\n <!-- Multi-app mode: grouped by application -->\n @for (group of filteredAppEntityGroups; track group.applicationId) {\n <div class=\"app-group\">\n <div class=\"app-group-header\" (click)=\"toggleAppGroup(group.applicationId)\">\n <div\n class=\"app-group-icon\"\n [style.background]=\"group.applicationColor ? group.applicationColor + '15' : null\"\n [style.color]=\"group.applicationColor || null\">\n <i [class]=\"group.applicationIcon || 'fa-solid fa-folder'\"></i>\n </div>\n <span class=\"app-group-name\">{{ group.applicationName }}</span>\n <span class=\"app-group-count\">{{ group.entities.length }}</span>\n <i class=\"fa-solid fa-chevron-right app-group-chevron\"\n [class.expanded]=\"group.isExpanded\"></i>\n </div>\n @if (group.isExpanded) {\n <div class=\"app-group-entities\">\n <div class=\"entity-item-grid\">\n @for (entity of group.entities; track entity.ID) {\n <div\n class=\"entity-item\"\n (click)=\"onEntitySelected(entity)\"\n [title]=\"entity.Description || entity.DisplayNameOrName\">\n <div class=\"entity-item-icon\">\n <i [class]=\"getEntityIcon(entity)\"></i>\n </div>\n <div class=\"entity-item-text\">\n <span class=\"entity-item-name\">{{ entity.DisplayNameOrName }}</span>\n @if (entity.Description) {\n <span class=\"entity-item-desc\">{{ entity.Description }}</span>\n }\n </div>\n <button\n class=\"entity-item-fav\"\n [class.favorited]=\"isEntityFavorited(entity)\"\n (click)=\"toggleEntityFavorite(entity, $event)\"\n [title]=\"isEntityFavorited(entity) ? 'Remove from favorites' : 'Add to favorites'\">\n <i [class]=\"isEntityFavorited(entity) ? 'fa-solid fa-star' : 'fa-regular fa-star'\"></i>\n </button>\n </div>\n }\n </div>\n </div>\n }\n </div>\n }\n }\n\n <!-- No results -->\n @if (filteredEntityCount === 0 && entities.length > 0) {\n <mj-empty-state Variant=\"no-results\"\n Title=\"No matches found\"\n [Message]=\"NoEntityResultsMessage\" />\n }\n @if (entities.length === 0) {\n <mj-empty-state Icon=\"fa-solid fa-database\"\n Title=\"Nothing here yet\"\n Message=\"This app doesn't have any data set up yet. An administrator can add data for you to explore here.\" />\n }\n </div>\n </div>\n\n <!-- Quick Access Panel (right slide-in) -->\n <div class=\"quick-access-panel\" [class.open]=\"state.quickAccessPanelOpen\">\n <div class=\"qa-header\">\n <h3>Quick Access</h3>\n <button class=\"qa-close-btn\" (click)=\"toggleQuickAccessPanel()\">\n <i class=\"fa-solid fa-xmark\"></i>\n </button>\n </div>\n <div class=\"qa-body\">\n <!-- Recent Records (max 3) -->\n <mj-accordion-panel [Expanded]=\"isQuickAccessSectionExpanded('recentRecords')\" (ExpandedChange)=\"toggleQuickAccessSection('recentRecords')\">\n <ng-template mjAccordionTitle>\n <i class=\"fa-solid fa-clock-rotate-left qa-section-icon\"></i>\n <span>Recent Records</span>\n <span class=\"mj-accordion-badge mj-accordion-badge--right\">{{ quickAccessRecentRecords.length }}</span>\n </ng-template>\n @for (record of quickAccessRecentRecords; track record.entityId + '|' + record.recordId) {\n <div class=\"qa-item\" (click)=\"onRecentRecordClick(record)\">\n <div class=\"qa-item-icon\">\n <i [class]=\"getEntityIconById(record.entityId)\"></i>\n </div>\n <div class=\"qa-item-info\">\n <div class=\"qa-item-name\">{{ record.recordName || record.recordId }}</div>\n <div class=\"qa-item-meta\">{{ getEntityDisplayName(record.entityName) }}</div>\n </div>\n <span class=\"qa-item-time\">{{ record.relativeTime }}</span>\n </div>\n }\n @if (quickAccessRecentRecords.length === 0) {\n <div class=\"qa-empty\">No recent records</div>\n }\n </mj-accordion-panel>\n\n <!-- Recent Entities (max 3) -->\n <mj-accordion-panel [Expanded]=\"isQuickAccessSectionExpanded('recentEntities')\" (ExpandedChange)=\"toggleQuickAccessSection('recentEntities')\">\n <ng-template mjAccordionTitle>\n <i class=\"fa-solid fa-table qa-section-icon\"></i>\n <span>Recent Record Types</span>\n <span class=\"mj-accordion-badge mj-accordion-badge--right\">{{ quickAccessRecentEntities.length }}</span>\n </ng-template>\n @for (entity of quickAccessRecentEntities; track entity.ID) {\n <div class=\"qa-item\" (click)=\"onEntitySelected(entity)\">\n <div class=\"qa-item-icon\">\n <i [class]=\"getEntityIcon(entity)\"></i>\n </div>\n <div class=\"qa-item-info\">\n <div class=\"qa-item-name\">{{ entity.DisplayNameOrName }}</div>\n </div>\n </div>\n }\n @if (quickAccessRecentEntities.length === 0) {\n <div class=\"qa-empty\">No recent record types</div>\n }\n </mj-accordion-panel>\n\n <!-- Favorite Records (max 3) -->\n <mj-accordion-panel [Expanded]=\"isQuickAccessSectionExpanded('favoriteRecords')\" (ExpandedChange)=\"toggleQuickAccessSection('favoriteRecords')\">\n <ng-template mjAccordionTitle>\n <i class=\"fa-solid fa-star qa-section-icon\" style=\"color: var(--mj-status-warning);\"></i>\n <span>Favorite Records</span>\n <span class=\"mj-accordion-badge mj-accordion-badge--right\">{{ quickAccessFavoriteRecords.length }}</span>\n </ng-template>\n @for (record of quickAccessFavoriteRecords; track record.userFavoriteId) {\n <div class=\"qa-item\" (click)=\"onFavoriteRecordClick(record)\">\n <div class=\"qa-item-icon\">\n <i [class]=\"getEntityIconById(record.entityId)\"></i>\n </div>\n <div class=\"qa-item-info\">\n <div class=\"qa-item-name\">{{ record.recordName || record.recordId }}</div>\n <div class=\"qa-item-meta\">{{ getEntityDisplayName(record.entityName) }}</div>\n </div>\n </div>\n }\n @if (quickAccessFavoriteRecords.length === 0) {\n <div class=\"qa-empty\">No favorite records</div>\n }\n </mj-accordion-panel>\n </div>\n </div>\n }\n </div>\n } @else {\n <!--\n mj-view-workspace owns the saved-view lifecycle (select / save / duplicate / rename /\n delete / revert / quick-save) and the inner data renderer. Every view type is now a\n self-contained plug-in: view-type-specific features (export / add-to-list / delete /\n grid-state / timeline + map controls) live INSIDE the plug-in renderers and no longer\n bubble through the workspace. The dashboard only handles navigation (open record / open\n related record / create new record) + the generic filter/data-load/count signals + URL\n and view-CRUD state sync. AutoSaveView=true \u2192 the workspace persists view CRUD itself.\n -->\n <mj-view-workspace\n [Entity]=\"selectedEntity\"\n [AutoSaveView]=\"true\"\n [SelectedView]=\"selectedViewEntity\"\n [AlternateBaseView]=\"SelectedAlternateBaseView\"\n [FilterText]=\"debouncedFilterText\"\n [SelectedRecordId]=\"state.selectedRecordId\"\n [ViewerConfig]=\"viewerConfig\"\n (SelectedViewChange)=\"onWorkspaceViewSelected($event)\"\n (ViewSelected)=\"onWorkspaceViewSelected($event)\"\n (AlternateBaseViewChange)=\"OnWorkspaceAlternateBaseViewChanged($event)\"\n (OpenViewInTabRequested)=\"onOpenInTabRequested($event)\"\n (OpenRecordRequested)=\"onWorkspaceOpenRecord($event)\"\n (OpenRelatedRecordRequested)=\"onOpenRelatedRecordRequested($event)\"\n (CreateNewRecordRequested)=\"onCreateNewRecordRequested($event)\"\n (RecordSelected)=\"onViewerRecordSelected($event)\"\n (AfterViewSave)=\"onWorkspaceViewSaved()\"\n (AfterViewDelete)=\"onWorkspaceViewSaved()\"\n (FilterTextChanged)=\"onFilterTextChanged($event)\"\n (DataLoaded)=\"onDataLoaded($event)\"\n (FilteredCountChanged)=\"onFilteredCountChanged($event)\">\n </mj-view-workspace>\n }\n </div>\n </div>\n\n <!-- Detail Panel (Right - Slide In) -->\n @if (state.detailPanelOpen && selectedRecord) {\n <div class=\"detail-panel\" [style.width.px]=\"state.detailPanelWidth\">\n <mj-entity-record-detail-panel\n [entity]=\"detailPanelEntity\"\n [record]=\"selectedRecord\"\n (close)=\"onDetailPanelClosed()\"\n (openRecord)=\"onOpenRecord($event)\"\n (navigateToRelated)=\"onNavigateToRelated($event)\"\n (openRelatedRecord)=\"onOpenRelatedRecord($event)\"\n (openForeignKeyRecord)=\"onOpenForeignKeyRecord($event)\">\n </mj-entity-record-detail-panel>\n </div>\n }\n\n <!-- View config panel, filter dialog, quick-save/duplicate/shared-view-warning dialogs\n now live inside mj-view-workspace (the saved-view lifecycle moved there).\n Export + Add-to-List dialogs now live inside the grid plug-in renderer (it hosts its own\n Generic dialogs), so they are no longer rendered at the dashboard level. -->\n</div>\n", styles: [".data-explorer-container {\n display: flex;\n height: 100%;\n width: 100%;\n background: var(--mj-bg-surface-card);\n overflow: hidden;\n}\n\n.navigation-panel {\n flex-shrink: 0;\n height: 100%;\n background: var(--mj-bg-surface);\n border-right: 1px solid var(--mj-border-default);\n transition: width 0.2s ease-in-out;\n overflow: hidden;\n box-shadow: 2px 0 8px rgba(0, 0, 0, 0.04);\n /* Isolate the navigation panel stacking context so its internal .collapse-toggle\n (z-index: 10) never leaks out above .content-body or full-screen dialogs. */\n isolation: isolate;\n position: relative;\n z-index: 1;\n}\n.navigation-panel.collapsed {\n width: 48px;\n}\n\n.content-area {\n flex: 1;\n display: flex;\n flex-direction: column;\n height: 100%;\n min-width: 0;\n overflow: hidden;\n background: var(--mj-bg-surface-card);\n}\n\n/* Breadcrumb Bar */\n.breadcrumb-bar {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 24px;\n background: var(--mj-bg-surface);\n border-bottom: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n font-size: 13px;\n min-height: 40px;\n}\n\n.breadcrumb-item {\n display: flex;\n align-items: center;\n gap: 6px;\n color: var(--mj-text-secondary);\n padding: 4px 8px;\n border-radius: 4px;\n transition: all 0.15s ease;\n max-width: 200px;\n}\n\n.breadcrumb-item.clickable {\n cursor: pointer;\n}\n\n.breadcrumb-item.clickable:hover {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-brand-primary);\n}\n\n.breadcrumb-item.current {\n color: var(--mj-text-primary);\n font-weight: 500;\n cursor: default;\n}\n\n.breadcrumb-icon {\n font-size: 12px;\n flex-shrink: 0;\n}\n\n.breadcrumb-label {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.breadcrumb-separator {\n font-size: 10px;\n color: var(--mj-border-strong);\n flex-shrink: 0;\n}\n\n.content-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px 24px;\n background: var(--mj-bg-surface);\n border-bottom: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n gap: 24px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);\n position: relative;\n z-index: 2;\n}\n\n.header-left {\n display: flex;\n align-items: center;\n gap: 12px;\n flex-shrink: 0;\n flex-wrap: wrap;\n}\n\n/* View Selector within header */\n.header-left ::ng-deep mj-view-selector {\n margin-left: 8px;\n}\n\n.entity-icon {\n font-size: 20px;\n color: var(--mj-brand-primary);\n}\n\n.entity-title {\n margin: 0;\n font-size: 20px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.record-count {\n font-size: 13px;\n color: var(--mj-text-secondary);\n font-weight: 500;\n}\n\n.header-center {\n flex: 1;\n max-width: 600px;\n}\n\n.smart-filter-container {\n position: relative;\n width: 100%;\n}\n\n.smart-filter-input {\n width: 100%;\n padding: 10px 40px 10px 16px;\n border: 1px solid var(--mj-border-strong);\n border-radius: 8px;\n font-size: 14px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n transition: all 0.15s ease;\n}\n.smart-filter-input:focus {\n outline: none;\n border-color: var(--mj-brand-primary);\n background: var(--mj-bg-surface);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n}\n.smart-filter-input::placeholder {\n color: var(--mj-text-muted);\n}\n\n.clear-filter-btn {\n position: absolute;\n right: 8px;\n top: 50%;\n transform: translateY(-50%);\n width: 28px;\n height: 28px;\n border: none;\n background: transparent;\n border-radius: 50%;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-muted);\n transition: all 0.15s ease;\n}\n.clear-filter-btn:hover {\n background: var(--mj-border-default);\n color: var(--mj-text-primary);\n}\n\n.header-right {\n display: flex;\n align-items: center;\n gap: 16px;\n flex-shrink: 0;\n}\n\n.view-mode-toggle {\n display: flex;\n background: var(--mj-bg-surface-sunken);\n border-radius: 8px;\n padding: 3px;\n}\n\n.toggle-btn {\n width: 36px;\n height: 32px;\n border: none;\n background: transparent;\n border-radius: 6px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-secondary);\n transition: all 0.15s ease;\n}\n.toggle-btn:hover {\n color: var(--mj-text-primary);\n}\n.toggle-btn.active {\n background: var(--mj-bg-surface);\n color: var(--mj-brand-primary);\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n}\n\n/* Header Action Button (Add to List, etc.) */\n.header-action-btn {\n position: relative;\n width: 36px;\n height: 36px;\n border: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n border-radius: 8px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-secondary);\n transition: all 0.15s ease;\n margin-left: 8px;\n}\n.header-action-btn:hover:not(.disabled) {\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 5%, var(--mj-bg-surface));\n}\n.header-action-btn.disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n.header-action-btn i {\n font-size: 14px;\n}\n.selection-badge {\n position: absolute;\n top: -6px;\n right: -6px;\n min-width: 18px;\n height: 18px;\n padding: 0 5px;\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n font-size: 11px;\n font-weight: 600;\n border-radius: 9px;\n display: flex;\n align-items: center;\n justify-content: center;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);\n}\n\n/* Timeline Date Field Selector - Styled Dropdown */\n.date-field-selector-container {\n display: flex;\n align-items: center;\n position: relative;\n}\n\n.date-field-backdrop {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n z-index: 999;\n background: transparent;\n}\n\n.date-field-selector-wrapper {\n position: relative;\n}\n\n.date-field-selector-button {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 6px 12px;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n cursor: pointer;\n font-size: 13px;\n color: var(--mj-text-primary);\n transition: all 0.15s ease;\n min-width: 120px;\n max-width: 200px;\n}\n\n.date-field-selector-button:hover:not(:disabled) {\n background: var(--mj-bg-surface-hover);\n border-color: var(--mj-border-strong);\n}\n\n.date-field-selector-button:disabled {\n cursor: default;\n opacity: 0.8;\n}\n\n.date-field-selector-button.open {\n background: var(--mj-bg-surface-active);\n border-color: var(--mj-brand-primary);\n}\n\n.date-field-icon {\n color: var(--mj-text-secondary);\n font-size: 12px;\n}\n\n.date-field-name {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n text-align: left;\n}\n\n.date-field-arrow {\n color: var(--mj-text-secondary);\n font-size: 10px;\n transition: transform 0.2s ease;\n}\n\n.date-field-arrow.rotated {\n transform: rotate(180deg);\n}\n\n.date-field-dropdown-panel {\n position: absolute;\n top: calc(100% + 4px);\n left: 0;\n min-width: 180px;\n max-width: 280px;\n max-height: 300px;\n overflow-y: auto;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);\n z-index: 1000;\n}\n\n.date-field-dropdown-item {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 10px 14px;\n cursor: pointer;\n transition: background 0.1s ease;\n font-size: 13px;\n}\n\n.date-field-dropdown-item:hover {\n background: var(--mj-bg-surface-card);\n}\n\n.date-field-dropdown-item.selected {\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n}\n\n.date-field-dropdown-item.selected:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n}\n\n.date-field-dropdown-item .item-icon {\n color: var(--mj-text-secondary);\n font-size: 13px;\n width: 16px;\n text-align: center;\n}\n\n.date-field-dropdown-item .item-name {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.date-field-dropdown-item .selected-check {\n color: var(--mj-brand-primary);\n font-size: 12px;\n}\n\n/* View-specific controls \u2014 contextual controls for the active view mode.\n Positioned LEFT of the view mode toggle with a subtle visual separator. */\n.view-specific-controls {\n display: flex;\n align-items: center;\n gap: 4px;\n padding: 3px 8px;\n background: var(--mj-bg-surface-sunken);\n border-radius: 8px;\n border: 1px solid var(--mj-border-subtle);\n}\n\n.view-specific-btn {\n width: 30px;\n height: 30px;\n border: none;\n background: transparent;\n border-radius: 6px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-muted);\n transition: all 0.15s ease;\n}\n\n.view-specific-btn:hover {\n color: var(--mj-text-secondary);\n background: var(--mj-bg-surface-hover);\n}\n\n/* Legacy \u2014 keep for backward compat but no longer used in template */\n.timeline-orientation-toggle {\n display: flex;\n background: var(--mj-bg-surface-sunken);\n border-radius: 8px;\n padding: 3px;\n}\n\n.content-body {\n display: flex;\n flex: 1 1 0%;\n overflow: hidden;\n /* Intentionally no z-index or isolation here: .content-header sits at z-index: 2\n above normal body content, .dynamic-view-host isolates plug-in renderers such as\n Leaflet (whose internal controls reach z-index: 1000) at z-index: 1, and full-screen\n modal overlays (--mj-z-modal) hosted inside mj-view-workspace or mj-entity-data-grid\n can escape above .content-header without being trapped. */\n position: relative;\n}\n\n/* ============================================\n DASHBOARDS MOVED BANNER\n ============================================ */\n\n/* Sits between .content-header and .content-body in the .content-area column. Side margins line up with the header text. */\n.dx-moved-banner {\n display: flex;\n flex-wrap: wrap;\n flex-shrink: 0;\n align-items: center;\n gap: 10px;\n padding: 10px 14px;\n margin: 12px 24px;\n border-radius: 10px;\n background: var(--mj-status-info-bg);\n color: var(--mj-status-info-text);\n font-size: 13px;\n transition: margin-right 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n/* The home header has no bottom border and ends with its own padding. */\n.content-header.home-header + .dx-moved-banner {\n margin-top: 0;\n}\n\n/* The open Quick Access panel covers the right 320px, so the banner ends 24px before it. */\n.dx-moved-banner.panel-open {\n margin-right: 344px;\n}\n\n/* The text fills the free space. When less than 200px is free, the buttons go to a second line. */\n.dx-moved-banner span {\n flex: 1 1 200px;\n}\n\n.loading-container,\n.loading-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n gap: 16px;\n background: var(--mj-bg-surface);\n border-radius: 8px;\n}\n\n.loading-spinner {\n font-size: 32px;\n color: var(--mj-brand-primary);\n}\n\n.loading-message {\n margin: 0;\n font-size: 14px;\n color: var(--mj-text-secondary);\n}\n\n\n/* ============================================\n CONCEPT D: HOME VIEW LAYOUT\n ============================================ */\n\n.home-view-concept-d {\n flex: 1;\n display: flex;\n overflow: hidden;\n}\n\n.home-main-area {\n flex: 1;\n overflow-y: auto;\n transition: margin-right 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.home-main-area.panel-open {\n margin-right: 320px;\n}\n\n/* ============================================\n SEARCH HERO\n ============================================ */\n\n.search-hero {\n background: var(--mj-bg-surface);\n padding: 36px 40px 24px;\n text-align: center;\n}\n\n.search-hero-container {\n position: relative;\n max-width: 600px;\n margin: 0 auto;\n}\n\n.search-hero-input {\n width: 100%;\n padding: 14px 18px 14px 44px;\n font-size: 15px;\n border: 2px solid var(--mj-border-default);\n border-radius: 12px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n outline: none;\n transition: all 0.2s;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06);\n font-family: inherit;\n}\n\n.search-hero-input:focus {\n border-color: var(--mj-brand-primary);\n box-shadow: 0 12px 40px rgba(0, 0, 0, 0.1), 0 0 0 4px color-mix(in srgb, var(--mj-brand-primary) 8%, transparent);\n}\n\n.search-hero-input::placeholder {\n color: var(--mj-text-primary);\n}\n\n.search-hero-icon {\n position: absolute;\n left: 16px;\n top: 50%;\n transform: translateY(-50%);\n color: var(--mj-text-primary);\n font-size: 15px;\n pointer-events: none;\n}\n\n.search-hero-shortcut {\n position: absolute;\n right: 14px;\n top: 50%;\n transform: translateY(-50%);\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n padding: 2px 7px;\n border-radius: 5px;\n font-size: 12px;\n color: var(--mj-text-muted);\n}\n\n.search-hero-clear {\n position: absolute;\n right: 14px;\n top: 50%;\n transform: translateY(-50%);\n width: 28px;\n height: 28px;\n border: none;\n background: transparent;\n border-radius: 50%;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-muted);\n transition: all 0.15s ease;\n}\n\n.search-hero-clear:hover {\n background: var(--mj-border-default);\n color: var(--mj-text-primary);\n}\n\n/* Meta row: entity count + pills */\n.search-meta-row {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 12px;\n margin-top: 16px;\n}\n\n.search-entity-count {\n font-size: 13px;\n color: var(--mj-text-muted);\n}\n\n.pill-toggle {\n display: flex;\n gap: 6px;\n}\n\n.pill-btn {\n padding: 6px 16px;\n border-radius: 20px;\n border: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n font-size: 13px;\n color: var(--mj-text-secondary);\n cursor: pointer;\n transition: all 0.15s;\n font-family: inherit;\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n.pill-btn:hover {\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-primary);\n}\n\n.pill-btn.active {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n border-color: var(--mj-brand-primary);\n}\n\n.pill-btn i {\n font-size: 11px;\n}\n\n/* ============================================\n APPLICATION GROUPS\n ============================================ */\n\n.entity-groups-area {\n padding: 12px 40px 60px;\n max-width: 1200px;\n margin: 0 auto;\n}\n\n.app-group {\n margin-bottom: 4px;\n}\n\n.app-group-header {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 12px 14px;\n cursor: pointer;\n border-radius: 8px;\n transition: background 0.12s;\n user-select: none;\n}\n\n.app-group-header:hover {\n background: rgba(0, 0, 0, 0.025);\n}\n\n.app-group-icon {\n width: 38px;\n height: 38px;\n border-radius: 10px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 17px;\n flex-shrink: 0;\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n}\n\n.app-group-name {\n font-size: 21px;\n font-weight: 600;\n flex: 1;\n}\n\n.app-group-count {\n font-size: 12px;\n color: var(--mj-text-muted);\n background: var(--mj-bg-surface-card);\n padding: 2px 9px;\n border-radius: 10px;\n}\n\n.app-group-chevron {\n color: var(--mj-text-muted);\n font-size: 12px;\n transition: transform 0.2s;\n}\n\n.app-group-chevron.expanded {\n transform: rotate(90deg);\n}\n\n.app-group-entities {\n padding: 4px 14px 10px 60px;\n}\n\n/* ============================================\n ENTITY ITEM GRID (pills)\n ============================================ */\n\n.entity-item-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));\n gap: 12px;\n}\n\n.entity-item {\n display: flex;\n align-items: flex-start;\n gap: 12px;\n padding: 10px 14px;\n border-radius: 8px;\n cursor: pointer;\n transition: all 0.12s;\n border: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n}\n\n.entity-item:hover {\n border-color: color-mix(in srgb, var(--mj-brand-primary) 40%, var(--mj-bg-surface));\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);\n background: color-mix(in srgb, var(--mj-brand-primary) 5%, var(--mj-bg-surface));\n}\n\n.entity-item-icon {\n width: 32px;\n height: 32px;\n border-radius: 8px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 14px;\n color: var(--mj-text-secondary);\n background: var(--mj-bg-surface-card);\n flex-shrink: 0;\n}\n\n.entity-item:hover .entity-item-icon {\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n}\n\n.entity-item-text {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.entity-item-name {\n font-size: 14px;\n font-weight: 500;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.entity-item-desc {\n font-size: 12px;\n color: var(--mj-text-muted);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n line-height: 1.3;\n}\n\n.entity-item-fav {\n color: var(--mj-text-muted);\n opacity: 0;\n transition: opacity 0.12s;\n font-size: 11px;\n cursor: pointer;\n flex-shrink: 0;\n background: none;\n border: none;\n padding: 4px;\n align-self: center;\n}\n\n.entity-item:hover .entity-item-fav {\n opacity: 1;\n}\n\n.entity-item-fav.favorited {\n opacity: 1;\n color: var(--mj-status-warning);\n}\n\n/* ============================================\n QUICK ACCESS PANEL (right slide-in)\n ============================================ */\n\n.quick-access-panel {\n position: fixed;\n top: 60px;\n right: 0;\n width: 320px;\n height: calc(100% - 60px);\n background: var(--mj-bg-surface);\n border-left: 1px solid var(--mj-border-default);\n z-index: 50;\n transform: translateX(100%);\n transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n display: flex;\n flex-direction: column;\n}\n\n.quick-access-panel.open {\n transform: translateX(0);\n}\n\n.qa-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 12px 16px;\n border-bottom: 1px solid var(--mj-border-default);\n}\n\n.qa-header h3 {\n font-size: 14px;\n font-weight: 600;\n margin: 0;\n}\n\n.qa-close-btn {\n background: none;\n border: none;\n color: var(--mj-text-muted);\n cursor: pointer;\n font-size: 14px;\n padding: 4px;\n border-radius: 4px;\n}\n\n.qa-close-btn:hover {\n background: var(--mj-bg-surface-card);\n}\n\n.qa-body {\n flex: 1;\n overflow-y: auto;\n}\n\n.qa-item {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 8px;\n border-radius: 8px;\n cursor: pointer;\n transition: background 0.08s;\n}\n\n.qa-item:hover {\n background: var(--mj-bg-surface-card);\n}\n\n.qa-item-icon {\n width: 28px;\n height: 28px;\n border-radius: 6px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 12px;\n background: var(--mj-bg-surface-card);\n color: var(--mj-text-secondary);\n flex-shrink: 0;\n}\n\n.qa-item-info {\n flex: 1;\n min-width: 0;\n}\n\n.qa-item-name {\n font-size: 13px;\n font-weight: 500;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.qa-item-meta {\n font-size: 11px;\n color: var(--mj-text-muted);\n}\n\n.qa-item-time {\n font-size: 11px;\n color: var(--mj-text-muted);\n white-space: nowrap;\n}\n\n.qa-empty {\n padding: 20px 16px;\n text-align: center;\n color: var(--mj-text-muted);\n font-size: 13px;\n}\n\n/* Quick Access toggle button badge */\n.qa-badge {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n font-size: 10px;\n font-weight: 600;\n padding: 0 5px;\n border-radius: 8px;\n min-width: 16px;\n text-align: center;\n}\n\n.detail-panel {\n flex-shrink: 0;\n height: 100%;\n background: var(--mj-bg-surface);\n border-left: 1px solid var(--mj-border-default);\n box-shadow: -4px 0 16px rgba(0, 0, 0, 0.08);\n overflow: hidden;\n animation: slideIn 0.2s ease-out;\n display: flex;\n flex-direction: column;\n}\n\n.detail-panel mj-entity-record-detail-panel {\n flex: 1;\n min-height: 0;\n overflow: hidden;\n}\n\n@keyframes slideIn {\n from {\n transform: translateX(100%);\n opacity: 0;\n }\n to {\n transform: translateX(0);\n opacity: 1;\n }\n}\n\n/* Detail Panel Actions Bar */\n.detail-panel-actions {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n background: var(--mj-bg-surface-card);\n border-bottom: 1px solid var(--mj-border-default);\n}\n\n.detail-action-btn {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 6px 12px;\n border: none;\n border-radius: 6px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);\n}\n\n.detail-action-btn:hover {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n transform: translateY(-1px);\n box-shadow: 0 2px 4px color-mix(in srgb, var(--mj-brand-primary) 20%, transparent);\n}\n\n.detail-action-btn i {\n font-size: 12px;\n}\n\n:host ::ng-deep mj-explorer-grid-view,\n:host ::ng-deep mj-explorer-cards-view {\n flex: 1;\n display: flex;\n flex-direction: column;\n min-height: 0;\n}\n\n/* ============================================\n HOME SCREEN STYLES\n ============================================ */\n\n/* Home-level header adjustments */\n.content-header.home-header {\n border-bottom: none;\n background: transparent;\n box-shadow: none;\n padding: 16px 24px 8px;\n}\n\n.content-body.home-content {\n padding: 0;\n}\n\n/* Smart filter with search icon (entity-level filter) */\n.filter-icon {\n position: absolute;\n left: 14px;\n top: 50%;\n transform: translateY(-50%);\n color: var(--mj-text-muted);\n font-size: 14px;\n pointer-events: none;\n}\n\n.smart-filter-container .smart-filter-input {\n padding-left: 40px;\n}\n\n/* ============================================\n VIEW MODE TOGGLE OVERRIDES\n ============================================ */\n\n.view-mode-toggle .toggle-btn {\n width: auto;\n padding: 0 12px;\n gap: 6px;\n}\n\n.toggle-label {\n font-size: 12px;\n font-weight: 500;\n}\n\n/* ============================================\n RESPONSIVE STYLES\n ============================================ */\n\n@media (max-width: 1200px) {\n .entity-item-grid {\n grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));\n }\n}\n\n@media (max-width: 900px) {\n .content-header {\n flex-wrap: wrap;\n gap: 12px;\n }\n\n .header-center {\n order: 3;\n flex-basis: 100%;\n max-width: 100%;\n }\n\n .search-hero {\n padding: 24px 16px 16px;\n }\n\n .entity-groups-area {\n padding: 8px 16px 40px;\n }\n\n .app-group-entities {\n padding-left: 36px;\n }\n\n .home-main-area.panel-open {\n margin-right: 0;\n }\n\n .quick-access-panel {\n width: 100%;\n }\n\n /* The Quick Access panel covers the full width here, so the banner keeps its normal margin. */\n .dx-moved-banner.panel-open {\n margin-right: 24px;\n }\n}\n\n@media (max-width: 600px) {\n .data-explorer-container {\n flex-direction: column;\n }\n\n .navigation-panel {\n display: none;\n }\n\n .content-header {\n padding: 12px 16px;\n }\n\n .content-body {\n padding: 12px 16px;\n }\n\n /* The entity header has 16px side padding here. The home header keeps 24px. */\n .content-header:not(.home-header) + .dx-moved-banner {\n margin-left: 16px;\n margin-right: 16px;\n }\n\n .entity-item-grid {\n grid-template-columns: 1fr;\n }\n\n .view-mode-toggle {\n display: none;\n }\n\n .app-group-name {\n font-size: 17px;\n }\n\n .entity-item-name {\n font-size: 14px;\n }\n}\n"] }]
|
|
3466
3631
|
}], () => [{ type: i1.ExplorerStateService }, { type: i0.ChangeDetectorRef }, { type: i2.RecentAccessService }, { type: i0.NgZone }], { FilterInputRef: [{
|
|
3467
3632
|
type: ViewChild,
|
|
3468
3633
|
args: ['filterInput']
|
|
@@ -3495,5 +3660,5 @@ export { DataExplorerDashboardComponent };
|
|
|
3495
3660
|
type: HostListener,
|
|
3496
3661
|
args: ['document:keydown', ['$event']]
|
|
3497
3662
|
}] }); })();
|
|
3498
|
-
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(DataExplorerDashboardComponent, { className: "DataExplorerDashboardComponent", filePath: "src/DataExplorer/data-explorer-dashboard.component.ts", lineNumber:
|
|
3663
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(DataExplorerDashboardComponent, { className: "DataExplorerDashboardComponent", filePath: "src/DataExplorer/data-explorer-dashboard.component.ts", lineNumber: 68 }); })();
|
|
3499
3664
|
//# sourceMappingURL=data-explorer-dashboard.component.js.map
|