@memberjunction/ng-explorer-core 6.2.0-edge.0 → 6.2.0-edge.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app-routing.module.d.ts +23 -1
- package/dist/app-routing.module.d.ts.map +1 -1
- package/dist/app-routing.module.js +50 -14
- package/dist/app-routing.module.js.map +1 -1
- package/dist/generated/lazy-feature-config.d.ts +1 -1
- package/dist/generated/lazy-feature-config.d.ts.map +1 -1
- package/dist/generated/lazy-feature-config.js +12 -1
- package/dist/generated/lazy-feature-config.js.map +1 -1
- package/dist/lib/command-palette/command-palette.component.d.ts +7 -1
- package/dist/lib/command-palette/command-palette.component.d.ts.map +1 -1
- package/dist/lib/command-palette/command-palette.component.js +28 -14
- package/dist/lib/command-palette/command-palette.component.js.map +1 -1
- package/dist/lib/conversation-feedback/conversation-feedback.d.ts +103 -13
- package/dist/lib/conversation-feedback/conversation-feedback.d.ts.map +1 -1
- package/dist/lib/conversation-feedback/conversation-feedback.js +219 -67
- package/dist/lib/conversation-feedback/conversation-feedback.js.map +1 -1
- package/dist/lib/dashboard-preferences-dialog/dashboard-preferences-dialog.component.d.ts +64 -10
- package/dist/lib/dashboard-preferences-dialog/dashboard-preferences-dialog.component.d.ts.map +1 -1
- package/dist/lib/dashboard-preferences-dialog/dashboard-preferences-dialog.component.js +172 -66
- package/dist/lib/dashboard-preferences-dialog/dashboard-preferences-dialog.component.js.map +1 -1
- package/dist/lib/generic/resource-container-component.d.ts +19 -5
- package/dist/lib/generic/resource-container-component.d.ts.map +1 -1
- package/dist/lib/generic/resource-container-component.js +43 -13
- package/dist/lib/generic/resource-container-component.js.map +1 -1
- package/dist/lib/guards/entities.guard.d.ts +4 -0
- package/dist/lib/guards/entities.guard.d.ts.map +1 -1
- package/dist/lib/guards/entities.guard.js +10 -2
- package/dist/lib/guards/entities.guard.js.map +1 -1
- package/dist/lib/identity-claims/claim-redeem.component.d.ts +4 -0
- package/dist/lib/identity-claims/claim-redeem.component.d.ts.map +1 -1
- package/dist/lib/identity-claims/claim-redeem.component.js +10 -2
- package/dist/lib/identity-claims/claim-redeem.component.js.map +1 -1
- package/dist/lib/oauth/oauth-callback.component.d.ts +4 -0
- package/dist/lib/oauth/oauth-callback.component.d.ts.map +1 -1
- package/dist/lib/oauth/oauth-callback.component.js +10 -2
- package/dist/lib/oauth/oauth-callback.component.js.map +1 -1
- package/dist/lib/profile/profile-dialog.component.d.ts.map +1 -1
- package/dist/lib/profile/profile-dialog.component.js +194 -191
- package/dist/lib/profile/profile-dialog.component.js.map +1 -1
- package/dist/lib/resource-wrappers/artifact-resource.component.d.ts +20 -2
- package/dist/lib/resource-wrappers/artifact-resource.component.d.ts.map +1 -1
- package/dist/lib/resource-wrappers/artifact-resource.component.js +33 -9
- package/dist/lib/resource-wrappers/artifact-resource.component.js.map +1 -1
- package/dist/lib/resource-wrappers/chat-collections-resource.component.d.ts +80 -10
- package/dist/lib/resource-wrappers/chat-collections-resource.component.d.ts.map +1 -1
- package/dist/lib/resource-wrappers/chat-collections-resource.component.js +168 -56
- package/dist/lib/resource-wrappers/chat-collections-resource.component.js.map +1 -1
- package/dist/lib/resource-wrappers/chat-conversations-resource.component.d.ts +199 -25
- package/dist/lib/resource-wrappers/chat-conversations-resource.component.d.ts.map +1 -1
- package/dist/lib/resource-wrappers/chat-conversations-resource.component.js +534 -182
- package/dist/lib/resource-wrappers/chat-conversations-resource.component.js.map +1 -1
- package/dist/lib/resource-wrappers/chat-deeplink-params.d.ts +2 -0
- package/dist/lib/resource-wrappers/chat-deeplink-params.d.ts.map +1 -1
- package/dist/lib/resource-wrappers/chat-deeplink-params.js +5 -1
- package/dist/lib/resource-wrappers/chat-deeplink-params.js.map +1 -1
- package/dist/lib/resource-wrappers/chat-search-routing.d.ts +25 -0
- package/dist/lib/resource-wrappers/chat-search-routing.d.ts.map +1 -0
- package/dist/lib/resource-wrappers/chat-search-routing.js +24 -0
- package/dist/lib/resource-wrappers/chat-search-routing.js.map +1 -0
- package/dist/lib/resource-wrappers/chat-tasks-resource.component.d.ts +14 -2
- package/dist/lib/resource-wrappers/chat-tasks-resource.component.d.ts.map +1 -1
- package/dist/lib/resource-wrappers/chat-tasks-resource.component.js +32 -8
- package/dist/lib/resource-wrappers/chat-tasks-resource.component.js.map +1 -1
- package/dist/lib/resource-wrappers/compose-email-fallback.d.ts +46 -0
- package/dist/lib/resource-wrappers/compose-email-fallback.d.ts.map +1 -0
- package/dist/lib/resource-wrappers/compose-email-fallback.js +62 -0
- package/dist/lib/resource-wrappers/compose-email-fallback.js.map +1 -0
- package/dist/lib/resource-wrappers/dashboard-resource.component.d.ts +54 -8
- package/dist/lib/resource-wrappers/dashboard-resource.component.d.ts.map +1 -1
- package/dist/lib/resource-wrappers/dashboard-resource.component.js +138 -46
- package/dist/lib/resource-wrappers/dashboard-resource.component.js.map +1 -1
- package/dist/lib/resource-wrappers/livekit-room-resource.component.d.ts +223 -30
- package/dist/lib/resource-wrappers/livekit-room-resource.component.d.ts.map +1 -1
- package/dist/lib/resource-wrappers/livekit-room-resource.component.js +438 -118
- package/dist/lib/resource-wrappers/livekit-room-resource.component.js.map +1 -1
- package/dist/lib/resource-wrappers/search-results-resource.component.d.ts +7 -3
- package/dist/lib/resource-wrappers/search-results-resource.component.d.ts.map +1 -1
- package/dist/lib/resource-wrappers/search-results-resource.component.js +23 -15
- package/dist/lib/resource-wrappers/search-results-resource.component.js.map +1 -1
- package/dist/lib/resource-wrappers/view-resource.component.d.ts +74 -11
- package/dist/lib/resource-wrappers/view-resource.component.d.ts.map +1 -1
- package/dist/lib/resource-wrappers/view-resource.component.js +214 -68
- package/dist/lib/resource-wrappers/view-resource.component.js.map +1 -1
- package/dist/lib/services/server-connectivity.service.d.ts +9 -4
- package/dist/lib/services/server-connectivity.service.d.ts.map +1 -1
- package/dist/lib/services/server-connectivity.service.js +56 -12
- package/dist/lib/services/server-connectivity.service.js.map +1 -1
- package/dist/lib/services/startup-validation.service.d.ts +4 -0
- package/dist/lib/services/startup-validation.service.d.ts.map +1 -1
- package/dist/lib/services/startup-validation.service.js +10 -2
- package/dist/lib/services/startup-validation.service.js.map +1 -1
- package/dist/lib/services/system-validation.service.d.ts +13 -1
- package/dist/lib/services/system-validation.service.d.ts.map +1 -1
- package/dist/lib/services/system-validation.service.js +29 -5
- package/dist/lib/services/system-validation.service.js.map +1 -1
- package/dist/lib/shell/components/dialogs/app-access-dialog.component.d.ts +54 -4
- package/dist/lib/shell/components/dialogs/app-access-dialog.component.d.ts.map +1 -1
- package/dist/lib/shell/components/dialogs/app-access-dialog.component.js +130 -40
- package/dist/lib/shell/components/dialogs/app-access-dialog.component.js.map +1 -1
- package/dist/lib/shell/components/header/app-nav.component.d.ts +29 -1
- package/dist/lib/shell/components/header/app-nav.component.d.ts.map +1 -1
- package/dist/lib/shell/components/header/app-nav.component.js +56 -12
- package/dist/lib/shell/components/header/app-nav.component.js.map +1 -1
- package/dist/lib/shell/components/header/app-switcher.component.d.ts +33 -5
- package/dist/lib/shell/components/header/app-switcher.component.d.ts.map +1 -1
- package/dist/lib/shell/components/header/app-switcher.component.js +85 -29
- package/dist/lib/shell/components/header/app-switcher.component.js.map +1 -1
- package/dist/lib/shell/components/record-open/record-origin-crumb.component.js +1 -1
- package/dist/lib/shell/components/record-open/record-origin-crumb.component.js.map +1 -1
- package/dist/lib/shell/components/tabs/component-cache-manager.d.ts +50 -6
- package/dist/lib/shell/components/tabs/component-cache-manager.d.ts.map +1 -1
- package/dist/lib/shell/components/tabs/component-cache-manager.js +95 -22
- package/dist/lib/shell/components/tabs/component-cache-manager.js.map +1 -1
- package/dist/lib/shell/components/tabs/tab-container.component.d.ts +91 -9
- package/dist/lib/shell/components/tabs/tab-container.component.d.ts.map +1 -1
- package/dist/lib/shell/components/tabs/tab-container.component.js +257 -101
- package/dist/lib/shell/components/tabs/tab-container.component.js.map +1 -1
- package/dist/lib/shell/loading-themes.d.ts +16 -12
- package/dist/lib/shell/loading-themes.d.ts.map +1 -1
- package/dist/lib/shell/loading-themes.js +145 -137
- package/dist/lib/shell/loading-themes.js.map +1 -1
- package/dist/lib/shell/readiness-beacon.d.ts +2 -0
- package/dist/lib/shell/readiness-beacon.d.ts.map +1 -1
- package/dist/lib/shell/readiness-beacon.js +5 -1
- package/dist/lib/shell/readiness-beacon.js.map +1 -1
- package/dist/lib/shell/services/about-dialog.service.d.ts +6 -0
- package/dist/lib/shell/services/about-dialog.service.d.ts.map +1 -1
- package/dist/lib/shell/services/about-dialog.service.js +16 -4
- package/dist/lib/shell/services/about-dialog.service.js.map +1 -1
- package/dist/lib/shell/services/profile-dialog.service.d.ts +6 -0
- package/dist/lib/shell/services/profile-dialog.service.d.ts.map +1 -1
- package/dist/lib/shell/services/profile-dialog.service.js +16 -4
- package/dist/lib/shell/services/profile-dialog.service.js.map +1 -1
- package/dist/lib/shell/services/user-sharing-center-dialog.service.d.ts +6 -0
- package/dist/lib/shell/services/user-sharing-center-dialog.service.d.ts.map +1 -1
- package/dist/lib/shell/services/user-sharing-center-dialog.service.js +15 -3
- package/dist/lib/shell/services/user-sharing-center-dialog.service.js.map +1 -1
- package/dist/lib/shell/shell.component.d.ts +264 -39
- package/dist/lib/shell/shell.component.d.ts.map +1 -1
- package/dist/lib/shell/shell.component.js +697 -294
- package/dist/lib/shell/shell.component.js.map +1 -1
- package/dist/lib/single-dashboard/Components/add-item/add-item.component.d.ts +64 -8
- package/dist/lib/single-dashboard/Components/add-item/add-item.component.d.ts.map +1 -1
- package/dist/lib/single-dashboard/Components/add-item/add-item.component.js +129 -37
- package/dist/lib/single-dashboard/Components/add-item/add-item.component.js.map +1 -1
- package/dist/lib/single-dashboard/Components/delete-item/delete-item.component.d.ts +26 -2
- package/dist/lib/single-dashboard/Components/delete-item/delete-item.component.d.ts.map +1 -1
- package/dist/lib/single-dashboard/Components/delete-item/delete-item.component.js +48 -10
- package/dist/lib/single-dashboard/Components/delete-item/delete-item.component.js.map +1 -1
- package/dist/lib/single-dashboard/Components/edit-dashboard/edit-dashboard.component.d.ts +84 -6
- package/dist/lib/single-dashboard/Components/edit-dashboard/edit-dashboard.component.d.ts.map +1 -1
- package/dist/lib/single-dashboard/Components/edit-dashboard/edit-dashboard.component.js +133 -29
- package/dist/lib/single-dashboard/Components/edit-dashboard/edit-dashboard.component.js.map +1 -1
- package/dist/lib/single-dashboard/single-dashboard.component.d.ts +173 -23
- package/dist/lib/single-dashboard/single-dashboard.component.d.ts.map +1 -1
- package/dist/lib/single-dashboard/single-dashboard.component.js +383 -109
- package/dist/lib/single-dashboard/single-dashboard.component.js.map +1 -1
- package/dist/lib/single-list-detail/single-list-detail.component.d.ts +383 -55
- package/dist/lib/single-list-detail/single-list-detail.component.d.ts.map +1 -1
- package/dist/lib/single-list-detail/single-list-detail.component.js +962 -326
- package/dist/lib/single-list-detail/single-list-detail.component.js.map +1 -1
- package/dist/lib/single-query/single-query.component.d.ts +22 -2
- package/dist/lib/single-query/single-query.component.d.ts.map +1 -1
- package/dist/lib/single-query/single-query.component.js +40 -10
- package/dist/lib/single-query/single-query.component.js.map +1 -1
- package/dist/lib/single-record/single-record.component.d.ts +41 -3
- package/dist/lib/single-record/single-record.component.d.ts.map +1 -1
- package/dist/lib/single-record/single-record.component.js +71 -11
- package/dist/lib/single-record/single-record.component.js.map +1 -1
- package/dist/lib/single-search-result/single-search-result.component.d.ts +23 -3
- package/dist/lib/single-search-result/single-search-result.component.d.ts.map +1 -1
- package/dist/lib/single-search-result/single-search-result.component.js +42 -12
- package/dist/lib/single-search-result/single-search-result.component.js.map +1 -1
- package/dist/lib/system-validation/system-validation-banner.component.d.ts +12 -2
- package/dist/lib/system-validation/system-validation-banner.component.d.ts.map +1 -1
- package/dist/lib/system-validation/system-validation-banner.component.js +25 -5
- package/dist/lib/system-validation/system-validation-banner.component.js.map +1 -1
- package/dist/lib/user-menu/user-menu.types.d.ts +4 -0
- package/dist/lib/user-menu/user-menu.types.d.ts.map +1 -1
- package/dist/lib/user-menu/user-menu.types.js +10 -2
- package/dist/lib/user-menu/user-menu.types.js.map +1 -1
- package/dist/lib/user-notifications/user-notifications.component.d.ts +59 -7
- package/dist/lib/user-notifications/user-notifications.component.d.ts.map +1 -1
- package/dist/lib/user-notifications/user-notifications.component.js +151 -47
- package/dist/lib/user-notifications/user-notifications.component.js.map +1 -1
- package/dist/lib/user-profile/user-profile.component.d.ts +3 -2
- package/dist/lib/user-profile/user-profile.component.d.ts.map +1 -1
- package/dist/lib/user-profile/user-profile.component.js +5 -4
- package/dist/lib/user-profile/user-profile.component.js.map +1 -1
- package/package.json +52 -52
|
@@ -836,64 +836,320 @@ export class SingleListDetailComponent extends BaseAngularComponent {
|
|
|
836
836
|
* those numbers drift from reality until a full page reload because the
|
|
837
837
|
* stats component otherwise only loads on init.
|
|
838
838
|
*/
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
839
|
+
StatsRefreshTrigger = 0;
|
|
840
|
+
/** @deprecated Use {@link StatsRefreshTrigger}. */
|
|
841
|
+
get statsRefreshTrigger() {
|
|
842
|
+
return this.StatsRefreshTrigger;
|
|
843
|
+
}
|
|
844
|
+
/** @deprecated Use {@link StatsRefreshTrigger}. */
|
|
845
|
+
set statsRefreshTrigger(value) {
|
|
846
|
+
this.StatsRefreshTrigger = value;
|
|
847
|
+
}
|
|
848
|
+
bumpStatsRefresh() { this.StatsRefreshTrigger++; }
|
|
849
|
+
ListDetailGrid;
|
|
850
|
+
/** @deprecated Use {@link ListDetailGrid}. */
|
|
851
|
+
get listDetailGrid() {
|
|
852
|
+
return this.ListDetailGrid;
|
|
853
|
+
}
|
|
854
|
+
/** @deprecated Use {@link ListDetailGrid}. */
|
|
855
|
+
set listDetailGrid(value) {
|
|
856
|
+
this.ListDetailGrid = value;
|
|
857
|
+
}
|
|
842
858
|
// List record
|
|
843
|
-
|
|
844
|
-
|
|
859
|
+
ListRecord = null;
|
|
860
|
+
/** @deprecated Use {@link ListRecord}. */
|
|
861
|
+
get listRecord() {
|
|
862
|
+
return this.ListRecord;
|
|
863
|
+
}
|
|
864
|
+
/** @deprecated Use {@link ListRecord}. */
|
|
865
|
+
set listRecord(value) {
|
|
866
|
+
this.ListRecord = value;
|
|
867
|
+
}
|
|
868
|
+
ShowLoader = false;
|
|
869
|
+
/** @deprecated Use {@link ShowLoader}. */
|
|
870
|
+
get showLoader() {
|
|
871
|
+
return this.ShowLoader;
|
|
872
|
+
}
|
|
873
|
+
/** @deprecated Use {@link ShowLoader}. */
|
|
874
|
+
set showLoader(value) {
|
|
875
|
+
this.ShowLoader = value;
|
|
876
|
+
}
|
|
845
877
|
// Permission-level gating (Phase 2.8). Resolved lazily after the list
|
|
846
878
|
// loads. Capabilities is exposed to the template for `@if` gating; the
|
|
847
879
|
// server-side enforcement remains the source of truth — these flags
|
|
848
880
|
// are a UX convenience so users don't see buttons they'll be rejected on.
|
|
849
|
-
|
|
850
|
-
|
|
881
|
+
Capabilities = CapabilitiesForLevel(null);
|
|
882
|
+
/** @deprecated Use {@link Capabilities}. */
|
|
883
|
+
get capabilities() {
|
|
884
|
+
return this.Capabilities;
|
|
885
|
+
}
|
|
886
|
+
/** @deprecated Use {@link Capabilities}. */
|
|
887
|
+
set capabilities(value) {
|
|
888
|
+
this.Capabilities = value;
|
|
889
|
+
}
|
|
890
|
+
CurrentLevel = null;
|
|
891
|
+
/** @deprecated Use {@link CurrentLevel}. */
|
|
892
|
+
get currentLevel() {
|
|
893
|
+
return this.CurrentLevel;
|
|
894
|
+
}
|
|
895
|
+
/** @deprecated Use {@link CurrentLevel}. */
|
|
896
|
+
set currentLevel(value) {
|
|
897
|
+
this.CurrentLevel = value;
|
|
898
|
+
}
|
|
851
899
|
// Lineage / refresh-from-source state. `sourceViewName` is loaded after
|
|
852
900
|
// listRecord so the lineage badge can show a friendly view name; the
|
|
853
901
|
// refresh-mode default falls back to the list's RefreshMode field but
|
|
854
902
|
// can be overridden per-user via localStorage (see `loadLastUsedMode`).
|
|
855
|
-
|
|
903
|
+
SourceViewName = null;
|
|
904
|
+
/** @deprecated Use {@link SourceViewName}. */
|
|
905
|
+
get sourceViewName() {
|
|
906
|
+
return this.SourceViewName;
|
|
907
|
+
}
|
|
908
|
+
/** @deprecated Use {@link SourceViewName}. */
|
|
909
|
+
set sourceViewName(value) {
|
|
910
|
+
this.SourceViewName = value;
|
|
911
|
+
}
|
|
856
912
|
// Bulk-edit (Phase 5.2). Status, Move, Copy, Remove. Move flows through
|
|
857
913
|
// the delta-confirm dialog because it produces drops on the source list.
|
|
858
|
-
|
|
859
|
-
|
|
914
|
+
BulkStatus = '';
|
|
915
|
+
/** @deprecated Use {@link BulkStatus}. */
|
|
916
|
+
get bulkStatus() {
|
|
917
|
+
return this.BulkStatus;
|
|
918
|
+
}
|
|
919
|
+
/** @deprecated Use {@link BulkStatus}. */
|
|
920
|
+
set bulkStatus(value) {
|
|
921
|
+
this.BulkStatus = value;
|
|
922
|
+
}
|
|
923
|
+
IsApplyingBulkStatus = false;
|
|
924
|
+
/** @deprecated Use {@link IsApplyingBulkStatus}. */
|
|
925
|
+
get isApplyingBulkStatus() {
|
|
926
|
+
return this.IsApplyingBulkStatus;
|
|
927
|
+
}
|
|
928
|
+
/** @deprecated Use {@link IsApplyingBulkStatus}. */
|
|
929
|
+
set isApplyingBulkStatus(value) {
|
|
930
|
+
this.IsApplyingBulkStatus = value;
|
|
931
|
+
}
|
|
860
932
|
// Move / Copy state (mockups 23, 24). The picker dialog lists candidate
|
|
861
933
|
// target lists scoped to the same entity. We load it lazily on first
|
|
862
934
|
// open and cache for the session — opening the picker twice in a row
|
|
863
935
|
// doesn't re-fetch unless the user explicitly hits "Refresh".
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
936
|
+
ShowMoveCopyDialog = false;
|
|
937
|
+
/** @deprecated Use {@link ShowMoveCopyDialog}. */
|
|
938
|
+
get showMoveCopyDialog() {
|
|
939
|
+
return this.ShowMoveCopyDialog;
|
|
940
|
+
}
|
|
941
|
+
/** @deprecated Use {@link ShowMoveCopyDialog}. */
|
|
942
|
+
set showMoveCopyDialog(value) {
|
|
943
|
+
this.ShowMoveCopyDialog = value;
|
|
944
|
+
}
|
|
945
|
+
MoveCopyMode = 'move';
|
|
946
|
+
/** @deprecated Use {@link MoveCopyMode}. */
|
|
947
|
+
get moveCopyMode() {
|
|
948
|
+
return this.MoveCopyMode;
|
|
949
|
+
}
|
|
950
|
+
/** @deprecated Use {@link MoveCopyMode}. */
|
|
951
|
+
set moveCopyMode(value) {
|
|
952
|
+
this.MoveCopyMode = value;
|
|
953
|
+
}
|
|
954
|
+
MoveCopyTargetSearch = '';
|
|
955
|
+
/** @deprecated Use {@link MoveCopyTargetSearch}. */
|
|
956
|
+
get moveCopyTargetSearch() {
|
|
957
|
+
return this.MoveCopyTargetSearch;
|
|
958
|
+
}
|
|
959
|
+
/** @deprecated Use {@link MoveCopyTargetSearch}. */
|
|
960
|
+
set moveCopyTargetSearch(value) {
|
|
961
|
+
this.MoveCopyTargetSearch = value;
|
|
962
|
+
}
|
|
963
|
+
MoveCopyTargetCandidates = [];
|
|
964
|
+
/** @deprecated Use {@link MoveCopyTargetCandidates}. */
|
|
965
|
+
get moveCopyTargetCandidates() {
|
|
966
|
+
return this.MoveCopyTargetCandidates;
|
|
967
|
+
}
|
|
968
|
+
/** @deprecated Use {@link MoveCopyTargetCandidates}. */
|
|
969
|
+
set moveCopyTargetCandidates(value) {
|
|
970
|
+
this.MoveCopyTargetCandidates = value;
|
|
971
|
+
}
|
|
972
|
+
MoveCopyTargetCandidatesLoading = false;
|
|
973
|
+
/** @deprecated Use {@link MoveCopyTargetCandidatesLoading}. */
|
|
974
|
+
get moveCopyTargetCandidatesLoading() {
|
|
975
|
+
return this.MoveCopyTargetCandidatesLoading;
|
|
976
|
+
}
|
|
977
|
+
/** @deprecated Use {@link MoveCopyTargetCandidatesLoading}. */
|
|
978
|
+
set moveCopyTargetCandidatesLoading(value) {
|
|
979
|
+
this.MoveCopyTargetCandidatesLoading = value;
|
|
980
|
+
}
|
|
981
|
+
MoveCopySelectedTarget = null;
|
|
982
|
+
/** @deprecated Use {@link MoveCopySelectedTarget}. */
|
|
983
|
+
get moveCopySelectedTarget() {
|
|
984
|
+
return this.MoveCopySelectedTarget;
|
|
985
|
+
}
|
|
986
|
+
/** @deprecated Use {@link MoveCopySelectedTarget}. */
|
|
987
|
+
set moveCopySelectedTarget(value) {
|
|
988
|
+
this.MoveCopySelectedTarget = value;
|
|
989
|
+
}
|
|
990
|
+
IsApplyingMoveCopy = false;
|
|
991
|
+
/** @deprecated Use {@link IsApplyingMoveCopy}. */
|
|
992
|
+
get isApplyingMoveCopy() {
|
|
993
|
+
return this.IsApplyingMoveCopy;
|
|
994
|
+
}
|
|
995
|
+
/** @deprecated Use {@link IsApplyingMoveCopy}. */
|
|
996
|
+
set isApplyingMoveCopy(value) {
|
|
997
|
+
this.IsApplyingMoveCopy = value;
|
|
998
|
+
}
|
|
999
|
+
MoveCopyProgress = 0;
|
|
1000
|
+
/** @deprecated Use {@link MoveCopyProgress}. */
|
|
1001
|
+
get moveCopyProgress() {
|
|
1002
|
+
return this.MoveCopyProgress;
|
|
1003
|
+
}
|
|
1004
|
+
/** @deprecated Use {@link MoveCopyProgress}. */
|
|
1005
|
+
set moveCopyProgress(value) {
|
|
1006
|
+
this.MoveCopyProgress = value;
|
|
1007
|
+
}
|
|
1008
|
+
MoveCopyTotal = 0;
|
|
1009
|
+
/** @deprecated Use {@link MoveCopyTotal}. */
|
|
1010
|
+
get moveCopyTotal() {
|
|
1011
|
+
return this.MoveCopyTotal;
|
|
1012
|
+
}
|
|
1013
|
+
/** @deprecated Use {@link MoveCopyTotal}. */
|
|
1014
|
+
set moveCopyTotal(value) {
|
|
1015
|
+
this.MoveCopyTotal = value;
|
|
1016
|
+
}
|
|
873
1017
|
// Move delta-confirm. Built locally from the in-hand RecordIDs — no
|
|
874
1018
|
// server round-trip needed since we already know exactly what will be
|
|
875
1019
|
// added to the target and removed from the source. The drop-guard is
|
|
876
1020
|
// enforced at confirm time (mode='move' + ack checkbox).
|
|
877
|
-
|
|
878
|
-
|
|
1021
|
+
MoveDeltaConfirmVisible = false;
|
|
1022
|
+
/** @deprecated Use {@link MoveDeltaConfirmVisible}. */
|
|
1023
|
+
get moveDeltaConfirmVisible() {
|
|
1024
|
+
return this.MoveDeltaConfirmVisible;
|
|
1025
|
+
}
|
|
1026
|
+
/** @deprecated Use {@link MoveDeltaConfirmVisible}. */
|
|
1027
|
+
set moveDeltaConfirmVisible(value) {
|
|
1028
|
+
this.MoveDeltaConfirmVisible = value;
|
|
1029
|
+
}
|
|
1030
|
+
MoveDelta = null;
|
|
1031
|
+
/** @deprecated Use {@link MoveDelta}. */
|
|
1032
|
+
get moveDelta() {
|
|
1033
|
+
return this.MoveDelta;
|
|
1034
|
+
}
|
|
1035
|
+
/** @deprecated Use {@link MoveDelta}. */
|
|
1036
|
+
set moveDelta(value) {
|
|
1037
|
+
this.MoveDelta = value;
|
|
1038
|
+
}
|
|
879
1039
|
// Export picker (Phase 5.1, mockup 26). Opens before any export; lets
|
|
880
1040
|
// the user pick format + which entity fields to include. Fields are
|
|
881
1041
|
// resolved from EntityInfo on the loaded provider — no separate fetch.
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
1042
|
+
ShowExportDialog = false;
|
|
1043
|
+
/** @deprecated Use {@link ShowExportDialog}. */
|
|
1044
|
+
get showExportDialog() {
|
|
1045
|
+
return this.ShowExportDialog;
|
|
1046
|
+
}
|
|
1047
|
+
/** @deprecated Use {@link ShowExportDialog}. */
|
|
1048
|
+
set showExportDialog(value) {
|
|
1049
|
+
this.ShowExportDialog = value;
|
|
1050
|
+
}
|
|
1051
|
+
ExportFormat = 'excel';
|
|
1052
|
+
/** @deprecated Use {@link ExportFormat}. */
|
|
1053
|
+
get exportFormat() {
|
|
1054
|
+
return this.ExportFormat;
|
|
1055
|
+
}
|
|
1056
|
+
/** @deprecated Use {@link ExportFormat}. */
|
|
1057
|
+
set exportFormat(value) {
|
|
1058
|
+
this.ExportFormat = value;
|
|
1059
|
+
}
|
|
1060
|
+
ExportFields = [];
|
|
1061
|
+
/** @deprecated Use {@link ExportFields}. */
|
|
1062
|
+
get exportFields() {
|
|
1063
|
+
return this.ExportFields;
|
|
1064
|
+
}
|
|
1065
|
+
/** @deprecated Use {@link ExportFields}. */
|
|
1066
|
+
set exportFields(value) {
|
|
1067
|
+
this.ExportFields = value;
|
|
1068
|
+
}
|
|
1069
|
+
ExportRecordCount = 0;
|
|
1070
|
+
/** @deprecated Use {@link ExportRecordCount}. */
|
|
1071
|
+
get exportRecordCount() {
|
|
1072
|
+
return this.ExportRecordCount;
|
|
1073
|
+
}
|
|
1074
|
+
/** @deprecated Use {@link ExportRecordCount}. */
|
|
1075
|
+
set exportRecordCount(value) {
|
|
1076
|
+
this.ExportRecordCount = value;
|
|
1077
|
+
}
|
|
1078
|
+
IsExporting = false;
|
|
1079
|
+
/** @deprecated Use {@link IsExporting}. */
|
|
1080
|
+
get isExporting() {
|
|
1081
|
+
return this.IsExporting;
|
|
1082
|
+
}
|
|
1083
|
+
/** @deprecated Use {@link IsExporting}. */
|
|
1084
|
+
set isExporting(value) {
|
|
1085
|
+
this.IsExporting = value;
|
|
1086
|
+
}
|
|
1087
|
+
RefreshMode = 'Additive';
|
|
1088
|
+
/** @deprecated Use {@link RefreshMode}. */
|
|
1089
|
+
get refreshMode() {
|
|
1090
|
+
return this.RefreshMode;
|
|
1091
|
+
}
|
|
1092
|
+
/** @deprecated Use {@link RefreshMode}. */
|
|
1093
|
+
set refreshMode(value) {
|
|
1094
|
+
this.RefreshMode = value;
|
|
1095
|
+
}
|
|
1096
|
+
IsPreviewingRefresh = false;
|
|
1097
|
+
/** @deprecated Use {@link IsPreviewingRefresh}. */
|
|
1098
|
+
get isPreviewingRefresh() {
|
|
1099
|
+
return this.IsPreviewingRefresh;
|
|
1100
|
+
}
|
|
1101
|
+
/** @deprecated Use {@link IsPreviewingRefresh}. */
|
|
1102
|
+
set isPreviewingRefresh(value) {
|
|
1103
|
+
this.IsPreviewingRefresh = value;
|
|
1104
|
+
}
|
|
1105
|
+
IsApplyingRefresh = false;
|
|
1106
|
+
/** @deprecated Use {@link IsApplyingRefresh}. */
|
|
1107
|
+
get isApplyingRefresh() {
|
|
1108
|
+
return this.IsApplyingRefresh;
|
|
1109
|
+
}
|
|
1110
|
+
/** @deprecated Use {@link IsApplyingRefresh}. */
|
|
1111
|
+
set isApplyingRefresh(value) {
|
|
1112
|
+
this.IsApplyingRefresh = value;
|
|
1113
|
+
}
|
|
1114
|
+
RefreshDelta = null;
|
|
1115
|
+
/** @deprecated Use {@link RefreshDelta}. */
|
|
1116
|
+
get refreshDelta() {
|
|
1117
|
+
return this.RefreshDelta;
|
|
1118
|
+
}
|
|
1119
|
+
/** @deprecated Use {@link RefreshDelta}. */
|
|
1120
|
+
set refreshDelta(value) {
|
|
1121
|
+
this.RefreshDelta = value;
|
|
1122
|
+
}
|
|
1123
|
+
RefreshConfirmVisible = false;
|
|
1124
|
+
/** @deprecated Use {@link RefreshConfirmVisible}. */
|
|
1125
|
+
get refreshConfirmVisible() {
|
|
1126
|
+
return this.RefreshConfirmVisible;
|
|
1127
|
+
}
|
|
1128
|
+
/** @deprecated Use {@link RefreshConfirmVisible}. */
|
|
1129
|
+
set refreshConfirmVisible(value) {
|
|
1130
|
+
this.RefreshConfirmVisible = value;
|
|
1131
|
+
}
|
|
892
1132
|
// Grid state
|
|
893
|
-
|
|
894
|
-
|
|
1133
|
+
SelectedKeys = [];
|
|
1134
|
+
/** @deprecated Use {@link SelectedKeys}. */
|
|
1135
|
+
get selectedKeys() {
|
|
1136
|
+
return this.SelectedKeys;
|
|
1137
|
+
}
|
|
1138
|
+
/** @deprecated Use {@link SelectedKeys}. */
|
|
1139
|
+
set selectedKeys(value) {
|
|
1140
|
+
this.SelectedKeys = value;
|
|
1141
|
+
}
|
|
1142
|
+
RowCount = 0;
|
|
1143
|
+
/** @deprecated Use {@link RowCount}. */
|
|
1144
|
+
get rowCount() {
|
|
1145
|
+
return this.RowCount;
|
|
1146
|
+
}
|
|
1147
|
+
/** @deprecated Use {@link RowCount}. */
|
|
1148
|
+
set rowCount(value) {
|
|
1149
|
+
this.RowCount = value;
|
|
1150
|
+
}
|
|
895
1151
|
// Toolbar config - hide EDG toolbar, we'll use our own
|
|
896
|
-
|
|
1152
|
+
GridToolbarConfig = {
|
|
897
1153
|
showSearch: false,
|
|
898
1154
|
showRefresh: false,
|
|
899
1155
|
showAdd: false,
|
|
@@ -902,39 +1158,191 @@ export class SingleListDetailComponent extends BaseAngularComponent {
|
|
|
902
1158
|
showRowCount: false,
|
|
903
1159
|
showSelectionCount: false
|
|
904
1160
|
};
|
|
1161
|
+
/** @deprecated Use {@link GridToolbarConfig}. */
|
|
1162
|
+
get gridToolbarConfig() {
|
|
1163
|
+
return this.GridToolbarConfig;
|
|
1164
|
+
}
|
|
1165
|
+
/** @deprecated Use {@link GridToolbarConfig}. */
|
|
1166
|
+
set gridToolbarConfig(value) {
|
|
1167
|
+
this.GridToolbarConfig = value;
|
|
1168
|
+
}
|
|
905
1169
|
// Remove from list dialog
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
1170
|
+
ShowRemoveDialog = false;
|
|
1171
|
+
/** @deprecated Use {@link ShowRemoveDialog}. */
|
|
1172
|
+
get showRemoveDialog() {
|
|
1173
|
+
return this.ShowRemoveDialog;
|
|
1174
|
+
}
|
|
1175
|
+
/** @deprecated Use {@link ShowRemoveDialog}. */
|
|
1176
|
+
set showRemoveDialog(value) {
|
|
1177
|
+
this.ShowRemoveDialog = value;
|
|
1178
|
+
}
|
|
1179
|
+
IsRemoving = false;
|
|
1180
|
+
/** @deprecated Use {@link IsRemoving}. */
|
|
1181
|
+
get isRemoving() {
|
|
1182
|
+
return this.IsRemoving;
|
|
1183
|
+
}
|
|
1184
|
+
/** @deprecated Use {@link IsRemoving}. */
|
|
1185
|
+
set isRemoving(value) {
|
|
1186
|
+
this.IsRemoving = value;
|
|
1187
|
+
}
|
|
1188
|
+
RemoveProgress = 0;
|
|
1189
|
+
/** @deprecated Use {@link RemoveProgress}. */
|
|
1190
|
+
get removeProgress() {
|
|
1191
|
+
return this.RemoveProgress;
|
|
1192
|
+
}
|
|
1193
|
+
/** @deprecated Use {@link RemoveProgress}. */
|
|
1194
|
+
set removeProgress(value) {
|
|
1195
|
+
this.RemoveProgress = value;
|
|
1196
|
+
}
|
|
1197
|
+
RemoveTotal = 0;
|
|
1198
|
+
/** @deprecated Use {@link RemoveTotal}. */
|
|
1199
|
+
get removeTotal() {
|
|
1200
|
+
return this.RemoveTotal;
|
|
1201
|
+
}
|
|
1202
|
+
/** @deprecated Use {@link RemoveTotal}. */
|
|
1203
|
+
set removeTotal(value) {
|
|
1204
|
+
this.RemoveTotal = value;
|
|
1205
|
+
}
|
|
910
1206
|
// Add records dialog
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
1207
|
+
ShowAddRecordsDialog = false;
|
|
1208
|
+
/** @deprecated Use {@link ShowAddRecordsDialog}. */
|
|
1209
|
+
get showAddRecordsDialog() {
|
|
1210
|
+
return this.ShowAddRecordsDialog;
|
|
1211
|
+
}
|
|
1212
|
+
/** @deprecated Use {@link ShowAddRecordsDialog}. */
|
|
1213
|
+
set showAddRecordsDialog(value) {
|
|
1214
|
+
this.ShowAddRecordsDialog = value;
|
|
1215
|
+
}
|
|
1216
|
+
AddDialogLoading = false;
|
|
1217
|
+
/** @deprecated Use {@link AddDialogLoading}. */
|
|
1218
|
+
get addDialogLoading() {
|
|
1219
|
+
return this.AddDialogLoading;
|
|
1220
|
+
}
|
|
1221
|
+
/** @deprecated Use {@link AddDialogLoading}. */
|
|
1222
|
+
set addDialogLoading(value) {
|
|
1223
|
+
this.AddDialogLoading = value;
|
|
1224
|
+
}
|
|
1225
|
+
AddDialogSaving = false;
|
|
1226
|
+
/** @deprecated Use {@link AddDialogSaving}. */
|
|
1227
|
+
get addDialogSaving() {
|
|
1228
|
+
return this.AddDialogSaving;
|
|
1229
|
+
}
|
|
1230
|
+
/** @deprecated Use {@link AddDialogSaving}. */
|
|
1231
|
+
set addDialogSaving(value) {
|
|
1232
|
+
this.AddDialogSaving = value;
|
|
1233
|
+
}
|
|
1234
|
+
AddableRecords = [];
|
|
1235
|
+
/** @deprecated Use {@link AddableRecords}. */
|
|
1236
|
+
get addableRecords() {
|
|
1237
|
+
return this.AddableRecords;
|
|
1238
|
+
}
|
|
1239
|
+
/** @deprecated Use {@link AddableRecords}. */
|
|
1240
|
+
set addableRecords(value) {
|
|
1241
|
+
this.AddableRecords = value;
|
|
1242
|
+
}
|
|
1243
|
+
AddRecordsSearchFilter = "";
|
|
1244
|
+
/** @deprecated Use {@link AddRecordsSearchFilter}. */
|
|
1245
|
+
get addRecordsSearchFilter() {
|
|
1246
|
+
return this.AddRecordsSearchFilter;
|
|
1247
|
+
}
|
|
1248
|
+
/** @deprecated Use {@link AddRecordsSearchFilter}. */
|
|
1249
|
+
set addRecordsSearchFilter(value) {
|
|
1250
|
+
this.AddRecordsSearchFilter = value;
|
|
1251
|
+
}
|
|
916
1252
|
/** Empty-state title shown when an add-records search returns no matches. */
|
|
917
1253
|
get AddRecordsNoMatchTitle() {
|
|
918
|
-
return `No records found matching "${this.
|
|
1254
|
+
return `No records found matching "${this.AddRecordsSearchFilter}"`;
|
|
1255
|
+
}
|
|
1256
|
+
ExistingListDetailIds = new Set();
|
|
1257
|
+
/** @deprecated Use {@link ExistingListDetailIds}. */
|
|
1258
|
+
get existingListDetailIds() {
|
|
1259
|
+
return this.ExistingListDetailIds;
|
|
1260
|
+
}
|
|
1261
|
+
/** @deprecated Use {@link ExistingListDetailIds}. */
|
|
1262
|
+
set existingListDetailIds(value) {
|
|
1263
|
+
this.ExistingListDetailIds = value;
|
|
1264
|
+
}
|
|
1265
|
+
AddProgress = 0;
|
|
1266
|
+
/** @deprecated Use {@link AddProgress}. */
|
|
1267
|
+
get addProgress() {
|
|
1268
|
+
return this.AddProgress;
|
|
1269
|
+
}
|
|
1270
|
+
/** @deprecated Use {@link AddProgress}. */
|
|
1271
|
+
set addProgress(value) {
|
|
1272
|
+
this.AddProgress = value;
|
|
1273
|
+
}
|
|
1274
|
+
AddTotal = 0;
|
|
1275
|
+
/** @deprecated Use {@link AddTotal}. */
|
|
1276
|
+
get addTotal() {
|
|
1277
|
+
return this.AddTotal;
|
|
1278
|
+
}
|
|
1279
|
+
/** @deprecated Use {@link AddTotal}. */
|
|
1280
|
+
set addTotal(value) {
|
|
1281
|
+
this.AddTotal = value;
|
|
919
1282
|
}
|
|
920
|
-
existingListDetailIds = new Set();
|
|
921
|
-
addProgress = 0;
|
|
922
|
-
addTotal = 0;
|
|
923
1283
|
searchSubject = new Subject();
|
|
924
1284
|
destroy$ = new Subject();
|
|
925
1285
|
// Add from view dialog (existing)
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
1286
|
+
ShowAddFromViewDialog = false;
|
|
1287
|
+
/** @deprecated Use {@link ShowAddFromViewDialog}. */
|
|
1288
|
+
get showAddFromViewDialog() {
|
|
1289
|
+
return this.ShowAddFromViewDialog;
|
|
1290
|
+
}
|
|
1291
|
+
/** @deprecated Use {@link ShowAddFromViewDialog}. */
|
|
1292
|
+
set showAddFromViewDialog(value) {
|
|
1293
|
+
this.ShowAddFromViewDialog = value;
|
|
1294
|
+
}
|
|
1295
|
+
ShowAddFromViewLoader = false;
|
|
1296
|
+
/** @deprecated Use {@link ShowAddFromViewLoader}. */
|
|
1297
|
+
get showAddFromViewLoader() {
|
|
1298
|
+
return this.ShowAddFromViewLoader;
|
|
1299
|
+
}
|
|
1300
|
+
/** @deprecated Use {@link ShowAddFromViewLoader}. */
|
|
1301
|
+
set showAddFromViewLoader(value) {
|
|
1302
|
+
this.ShowAddFromViewLoader = value;
|
|
1303
|
+
}
|
|
1304
|
+
UserViews = null;
|
|
1305
|
+
/** @deprecated Use {@link UserViews}. */
|
|
1306
|
+
get userViews() {
|
|
1307
|
+
return this.UserViews;
|
|
1308
|
+
}
|
|
1309
|
+
/** @deprecated Use {@link UserViews}. */
|
|
1310
|
+
set userViews(value) {
|
|
1311
|
+
this.UserViews = value;
|
|
1312
|
+
}
|
|
1313
|
+
UserViewsToAdd = [];
|
|
1314
|
+
/** @deprecated Use {@link UserViewsToAdd}. */
|
|
1315
|
+
get userViewsToAdd() {
|
|
1316
|
+
return this.UserViewsToAdd;
|
|
1317
|
+
}
|
|
1318
|
+
/** @deprecated Use {@link UserViewsToAdd}. */
|
|
1319
|
+
set userViewsToAdd(value) {
|
|
1320
|
+
this.UserViewsToAdd = value;
|
|
1321
|
+
}
|
|
930
1322
|
/**
|
|
931
1323
|
* Normalized-UUID set of the IDs in {@link userViewsToAdd}, kept in sync with that
|
|
932
1324
|
* array. Lets {@link isViewSelected} (bound per-row in the dialog's @for, ~2x/row)
|
|
933
1325
|
* do an O(1) lookup instead of scanning the array with UUIDsEqual on every check.
|
|
934
1326
|
*/
|
|
935
1327
|
userViewsToAddIds = new Set();
|
|
936
|
-
|
|
937
|
-
|
|
1328
|
+
AddFromViewProgress = 0;
|
|
1329
|
+
/** @deprecated Use {@link AddFromViewProgress}. */
|
|
1330
|
+
get addFromViewProgress() {
|
|
1331
|
+
return this.AddFromViewProgress;
|
|
1332
|
+
}
|
|
1333
|
+
/** @deprecated Use {@link AddFromViewProgress}. */
|
|
1334
|
+
set addFromViewProgress(value) {
|
|
1335
|
+
this.AddFromViewProgress = value;
|
|
1336
|
+
}
|
|
1337
|
+
AddFromViewTotal = 0;
|
|
1338
|
+
/** @deprecated Use {@link AddFromViewTotal}. */
|
|
1339
|
+
get addFromViewTotal() {
|
|
1340
|
+
return this.AddFromViewTotal;
|
|
1341
|
+
}
|
|
1342
|
+
/** @deprecated Use {@link AddFromViewTotal}. */
|
|
1343
|
+
set addFromViewTotal(value) {
|
|
1344
|
+
this.AddFromViewTotal = value;
|
|
1345
|
+
}
|
|
938
1346
|
/**
|
|
939
1347
|
* Stored percent (0–100) for the progress bar. Backed instead of computed
|
|
940
1348
|
* via getter because the original getter recomputed on every Angular CD
|
|
@@ -943,30 +1351,62 @@ export class SingleListDetailComponent extends BaseAngularComponent {
|
|
|
943
1351
|
* ExpressionChangedAfterItHasBeenCheckedError floods in the console.
|
|
944
1352
|
* Updated via setAddFromViewProgress(...) at controlled points.
|
|
945
1353
|
*/
|
|
946
|
-
|
|
947
|
-
|
|
1354
|
+
AddFromViewProgressPercent = 0;
|
|
1355
|
+
/** @deprecated Use {@link AddFromViewProgressPercent}. */
|
|
1356
|
+
get addFromViewProgressPercent() {
|
|
1357
|
+
return this.AddFromViewProgressPercent;
|
|
1358
|
+
}
|
|
1359
|
+
/** @deprecated Use {@link AddFromViewProgressPercent}. */
|
|
1360
|
+
set addFromViewProgressPercent(value) {
|
|
1361
|
+
this.AddFromViewProgressPercent = value;
|
|
1362
|
+
}
|
|
1363
|
+
FetchingRecordsToSave = false;
|
|
1364
|
+
/** @deprecated Use {@link FetchingRecordsToSave}. */
|
|
1365
|
+
get fetchingRecordsToSave() {
|
|
1366
|
+
return this.FetchingRecordsToSave;
|
|
1367
|
+
}
|
|
1368
|
+
/** @deprecated Use {@link FetchingRecordsToSave}. */
|
|
1369
|
+
set fetchingRecordsToSave(value) {
|
|
1370
|
+
this.FetchingRecordsToSave = value;
|
|
1371
|
+
}
|
|
948
1372
|
// Dropdown button toggle state
|
|
949
|
-
|
|
1373
|
+
ShowAddDropdown = false;
|
|
1374
|
+
/** @deprecated Use {@link ShowAddDropdown}. */
|
|
1375
|
+
get showAddDropdown() {
|
|
1376
|
+
return this.ShowAddDropdown;
|
|
1377
|
+
}
|
|
1378
|
+
/** @deprecated Use {@link ShowAddDropdown}. */
|
|
1379
|
+
set showAddDropdown(value) {
|
|
1380
|
+
this.ShowAddDropdown = value;
|
|
1381
|
+
}
|
|
950
1382
|
// Dropdown menu options
|
|
951
|
-
|
|
1383
|
+
AddOptions = [
|
|
952
1384
|
{
|
|
953
1385
|
Text: 'Add Records',
|
|
954
1386
|
Description: 'Search and add specific records to this list',
|
|
955
1387
|
Icon: 'search',
|
|
956
|
-
Action: () => this.
|
|
1388
|
+
Action: () => this.OpenAddRecordsDialog()
|
|
957
1389
|
},
|
|
958
1390
|
{
|
|
959
1391
|
Text: 'Add From View',
|
|
960
1392
|
Description: 'Add all records from a saved view',
|
|
961
1393
|
Icon: 'folder',
|
|
962
|
-
Action: () => this.
|
|
1394
|
+
Action: () => this.OpenAddFromViewDialog()
|
|
963
1395
|
}
|
|
964
1396
|
];
|
|
1397
|
+
/** @deprecated Use {@link AddOptions}. */
|
|
1398
|
+
get addOptions() {
|
|
1399
|
+
return this.AddOptions;
|
|
1400
|
+
}
|
|
1401
|
+
/** @deprecated Use {@link AddOptions}. */
|
|
1402
|
+
set addOptions(value) {
|
|
1403
|
+
this.AddOptions = value;
|
|
1404
|
+
}
|
|
965
1405
|
onDocumentClick(event) {
|
|
966
|
-
if (this.
|
|
1406
|
+
if (this.ShowAddDropdown) {
|
|
967
1407
|
const target = event.target;
|
|
968
1408
|
if (!this.elementRef.nativeElement.querySelector('.add-dropdown-wrapper')?.contains(target)) {
|
|
969
|
-
this.
|
|
1409
|
+
this.ShowAddDropdown = false;
|
|
970
1410
|
}
|
|
971
1411
|
}
|
|
972
1412
|
}
|
|
@@ -1002,7 +1442,7 @@ export class SingleListDetailComponent extends BaseAngularComponent {
|
|
|
1002
1442
|
async loadListRecord() {
|
|
1003
1443
|
if (!this.ListID)
|
|
1004
1444
|
return;
|
|
1005
|
-
this.
|
|
1445
|
+
this.ShowLoader = true;
|
|
1006
1446
|
try {
|
|
1007
1447
|
const md = this.ProviderToUse;
|
|
1008
1448
|
// Build + load against a LOCAL handle. Only assign to
|
|
@@ -1014,20 +1454,20 @@ export class SingleListDetailComponent extends BaseAngularComponent {
|
|
|
1014
1454
|
const loadResult = await list.Load(this.ListID);
|
|
1015
1455
|
if (!loadResult) {
|
|
1016
1456
|
LogError("Error loading list with ID " + this.ListID, undefined, list.LatestResult);
|
|
1017
|
-
this.
|
|
1457
|
+
this.ListRecord = null;
|
|
1018
1458
|
}
|
|
1019
1459
|
else {
|
|
1020
|
-
this.
|
|
1460
|
+
this.ListRecord = list;
|
|
1021
1461
|
await this.loadLineageContext();
|
|
1022
1462
|
await this.loadCapabilities();
|
|
1023
1463
|
}
|
|
1024
1464
|
}
|
|
1025
1465
|
catch (error) {
|
|
1026
1466
|
LogError("Error loading list", undefined, error);
|
|
1027
|
-
this.
|
|
1467
|
+
this.ListRecord = null;
|
|
1028
1468
|
}
|
|
1029
1469
|
finally {
|
|
1030
|
-
this.
|
|
1470
|
+
this.ShowLoader = false;
|
|
1031
1471
|
this.cdr.detectChanges();
|
|
1032
1472
|
}
|
|
1033
1473
|
}
|
|
@@ -1040,19 +1480,19 @@ export class SingleListDetailComponent extends BaseAngularComponent {
|
|
|
1040
1480
|
* rather than blocking the rest of the detail view.
|
|
1041
1481
|
*/
|
|
1042
1482
|
async loadLineageContext() {
|
|
1043
|
-
if (!this.
|
|
1044
|
-
this.
|
|
1483
|
+
if (!this.ListRecord?.SourceViewID) {
|
|
1484
|
+
this.SourceViewName = null;
|
|
1045
1485
|
return;
|
|
1046
1486
|
}
|
|
1047
|
-
this.
|
|
1487
|
+
this.RefreshMode = this.loadLastUsedMode() ?? this.ListRecord.RefreshMode;
|
|
1048
1488
|
try {
|
|
1049
1489
|
const view = await this.ProviderToUse.GetEntityObject('MJ: User Views');
|
|
1050
|
-
const loaded = await view.Load(this.
|
|
1051
|
-
this.
|
|
1490
|
+
const loaded = await view.Load(this.ListRecord.SourceViewID);
|
|
1491
|
+
this.SourceViewName = loaded ? view.Name : null;
|
|
1052
1492
|
}
|
|
1053
1493
|
catch (e) {
|
|
1054
1494
|
LogError(`Failed to load source view name for list ${this.ListID}: ${e}`);
|
|
1055
|
-
this.
|
|
1495
|
+
this.SourceViewName = null;
|
|
1056
1496
|
}
|
|
1057
1497
|
}
|
|
1058
1498
|
/**
|
|
@@ -1062,16 +1502,16 @@ export class SingleListDetailComponent extends BaseAngularComponent {
|
|
|
1062
1502
|
* so we don't accidentally surface buttons the server will reject.
|
|
1063
1503
|
*/
|
|
1064
1504
|
async loadCapabilities() {
|
|
1065
|
-
if (!this.
|
|
1066
|
-
this.
|
|
1067
|
-
this.
|
|
1505
|
+
if (!this.ListRecord) {
|
|
1506
|
+
this.Capabilities = CapabilitiesForLevel(null);
|
|
1507
|
+
this.CurrentLevel = null;
|
|
1068
1508
|
return;
|
|
1069
1509
|
}
|
|
1070
1510
|
try {
|
|
1071
1511
|
const currentUserId = this.ProviderToUse.CurrentUser?.ID;
|
|
1072
1512
|
if (!currentUserId) {
|
|
1073
|
-
this.
|
|
1074
|
-
this.
|
|
1513
|
+
this.Capabilities = CapabilitiesForLevel('View');
|
|
1514
|
+
this.CurrentLevel = 'View';
|
|
1075
1515
|
return;
|
|
1076
1516
|
}
|
|
1077
1517
|
// Fast path: the list's UserID is its owner. Owners always have full
|
|
@@ -1079,25 +1519,29 @@ export class SingleListDetailComponent extends BaseAngularComponent {
|
|
|
1079
1519
|
// is implicit), so the permission-row lookup below would return null
|
|
1080
1520
|
// and incorrectly hide all edit/share/delete buttons. Mirrors the
|
|
1081
1521
|
// owner short-circuit in the server-side ListSharing.ResolveEffectivePermission.
|
|
1082
|
-
if (UUIDsEqual(this.
|
|
1083
|
-
this.
|
|
1084
|
-
this.
|
|
1522
|
+
if (UUIDsEqual(this.ListRecord.UserID, currentUserId)) {
|
|
1523
|
+
this.CurrentLevel = 'Owner';
|
|
1524
|
+
this.Capabilities = CapabilitiesForLevel('Owner');
|
|
1085
1525
|
return;
|
|
1086
1526
|
}
|
|
1087
1527
|
// Non-owner: resolve via ListSharingService (GraphQL). Never instantiate
|
|
1088
1528
|
// the server-side `ListSharing` class from a browser bundle.
|
|
1089
|
-
const level = (await this.listSharingService.getUserPermissionLevel(this.
|
|
1090
|
-
this.
|
|
1091
|
-
this.
|
|
1529
|
+
const level = (await this.listSharingService.getUserPermissionLevel(this.ListRecord.ID, currentUserId));
|
|
1530
|
+
this.CurrentLevel = level;
|
|
1531
|
+
this.Capabilities = CapabilitiesForLevel(level);
|
|
1092
1532
|
}
|
|
1093
1533
|
catch (e) {
|
|
1094
1534
|
LogError(`loadCapabilities failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
1095
|
-
this.
|
|
1096
|
-
this.
|
|
1535
|
+
this.Capabilities = CapabilitiesForLevel('View');
|
|
1536
|
+
this.CurrentLevel = 'View';
|
|
1097
1537
|
}
|
|
1098
1538
|
}
|
|
1539
|
+
get HasLineage() {
|
|
1540
|
+
return !!this.ListRecord?.SourceViewID;
|
|
1541
|
+
}
|
|
1542
|
+
/** @deprecated Use {@link HasLineage}. */
|
|
1099
1543
|
get hasLineage() {
|
|
1100
|
-
return
|
|
1544
|
+
return this.HasLineage;
|
|
1101
1545
|
}
|
|
1102
1546
|
loadLastUsedMode() {
|
|
1103
1547
|
try {
|
|
@@ -1123,57 +1567,65 @@ export class SingleListDetailComponent extends BaseAngularComponent {
|
|
|
1123
1567
|
* and opens the confirm dialog when it returns. The dialog enforces the
|
|
1124
1568
|
* acknowledgement UX; the server enforces the actual drop guard.
|
|
1125
1569
|
*/
|
|
1126
|
-
async
|
|
1127
|
-
if (!this.
|
|
1570
|
+
async OnRefreshFromSource() {
|
|
1571
|
+
if (!this.HasLineage || this.IsPreviewingRefresh)
|
|
1128
1572
|
return;
|
|
1129
|
-
this.
|
|
1130
|
-
this.
|
|
1573
|
+
this.IsPreviewingRefresh = true;
|
|
1574
|
+
this.RefreshDelta = null;
|
|
1131
1575
|
this.cdr.detectChanges();
|
|
1132
1576
|
try {
|
|
1133
1577
|
const provider = this.ProviderToUse;
|
|
1134
1578
|
const client = new GraphQLListsClient(provider);
|
|
1135
1579
|
const delta = await client.PreviewListDelta({
|
|
1136
1580
|
Target: this.ListID,
|
|
1137
|
-
Source: { kind: 'view', viewId: this.
|
|
1138
|
-
Mode: this.
|
|
1581
|
+
Source: { kind: 'view', viewId: this.ListRecord.SourceViewID },
|
|
1582
|
+
Mode: this.RefreshMode,
|
|
1139
1583
|
});
|
|
1140
|
-
this.
|
|
1141
|
-
this.
|
|
1584
|
+
this.RefreshDelta = delta;
|
|
1585
|
+
this.RefreshConfirmVisible = true;
|
|
1142
1586
|
}
|
|
1143
1587
|
catch (e) {
|
|
1144
1588
|
const message = e instanceof Error ? e.message : String(e);
|
|
1145
1589
|
this.sharedService.CreateSimpleNotification(`Refresh preview failed: ${message}`, 'error', 5000);
|
|
1146
1590
|
}
|
|
1147
1591
|
finally {
|
|
1148
|
-
this.
|
|
1592
|
+
this.IsPreviewingRefresh = false;
|
|
1149
1593
|
this.cdr.detectChanges();
|
|
1150
1594
|
}
|
|
1151
1595
|
}
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
this.
|
|
1596
|
+
/** @deprecated Use {@link OnRefreshFromSource}. */
|
|
1597
|
+
async onRefreshFromSource() {
|
|
1598
|
+
return this.OnRefreshFromSource();
|
|
1599
|
+
}
|
|
1600
|
+
OnRefreshConfirmCancel() {
|
|
1601
|
+
this.RefreshConfirmVisible = false;
|
|
1602
|
+
this.RefreshDelta = null;
|
|
1155
1603
|
this.cdr.detectChanges();
|
|
1156
1604
|
}
|
|
1157
|
-
|
|
1158
|
-
|
|
1605
|
+
/** @deprecated Use {@link OnRefreshConfirmCancel}. */
|
|
1606
|
+
onRefreshConfirmCancel() {
|
|
1607
|
+
return this.OnRefreshConfirmCancel();
|
|
1608
|
+
}
|
|
1609
|
+
async OnRefreshConfirmCommit(deltaToken) {
|
|
1610
|
+
if (!this.RefreshDelta)
|
|
1159
1611
|
return;
|
|
1160
|
-
this.
|
|
1612
|
+
this.IsApplyingRefresh = true;
|
|
1161
1613
|
this.cdr.detectChanges();
|
|
1162
1614
|
try {
|
|
1163
1615
|
const provider = this.ProviderToUse;
|
|
1164
1616
|
const client = new GraphQLListsClient(provider);
|
|
1165
1617
|
const result = await client.ApplyListDelta({
|
|
1166
|
-
Delta: { ...this.
|
|
1167
|
-
ConfirmDrops: (this.
|
|
1618
|
+
Delta: { ...this.RefreshDelta, DeltaToken: deltaToken },
|
|
1619
|
+
ConfirmDrops: (this.RefreshDelta.Counts.Remove ?? 0) > 0,
|
|
1168
1620
|
});
|
|
1169
1621
|
if (result.Success) {
|
|
1170
|
-
this.saveLastUsedMode(this.
|
|
1171
|
-
this.
|
|
1172
|
-
this.
|
|
1622
|
+
this.saveLastUsedMode(this.RefreshMode);
|
|
1623
|
+
this.RefreshConfirmVisible = false;
|
|
1624
|
+
this.RefreshDelta = null;
|
|
1173
1625
|
this.sharedService.CreateSimpleNotification(`Refresh applied: +${result.Counts?.Added ?? 0} / -${result.Counts?.Removed ?? 0}`, 'success', 3000);
|
|
1174
1626
|
// Reload list (for LastRefreshedAt) + grid in parallel.
|
|
1175
1627
|
await this.loadListRecord();
|
|
1176
|
-
this.
|
|
1628
|
+
this.RefreshGrid();
|
|
1177
1629
|
this.bumpStatsRefresh();
|
|
1178
1630
|
}
|
|
1179
1631
|
else {
|
|
@@ -1185,59 +1637,103 @@ export class SingleListDetailComponent extends BaseAngularComponent {
|
|
|
1185
1637
|
this.sharedService.CreateSimpleNotification(`Refresh failed: ${message}`, 'error', 5000);
|
|
1186
1638
|
}
|
|
1187
1639
|
finally {
|
|
1188
|
-
this.
|
|
1640
|
+
this.IsApplyingRefresh = false;
|
|
1189
1641
|
this.cdr.detectChanges();
|
|
1190
1642
|
}
|
|
1191
1643
|
}
|
|
1644
|
+
/** @deprecated Use {@link OnRefreshConfirmCommit}. */
|
|
1645
|
+
async onRefreshConfirmCommit(deltaToken) {
|
|
1646
|
+
return this.OnRefreshConfirmCommit(deltaToken);
|
|
1647
|
+
}
|
|
1648
|
+
OnRefreshModeChange(mode) {
|
|
1649
|
+
this.RefreshMode = mode;
|
|
1650
|
+
}
|
|
1651
|
+
/** @deprecated Use {@link OnRefreshModeChange}. */
|
|
1192
1652
|
onRefreshModeChange(mode) {
|
|
1193
|
-
this.
|
|
1653
|
+
return this.OnRefreshModeChange(mode);
|
|
1194
1654
|
}
|
|
1195
1655
|
// ==========================================
|
|
1196
1656
|
// Grid Event Handlers
|
|
1197
1657
|
// ==========================================
|
|
1198
|
-
|
|
1658
|
+
OnRowClicked(_event) {
|
|
1199
1659
|
// Selection is handled by the grid
|
|
1200
1660
|
}
|
|
1201
|
-
|
|
1661
|
+
/** @deprecated Use {@link OnRowClicked}. */
|
|
1662
|
+
onRowClicked(_event) {
|
|
1663
|
+
return this.OnRowClicked(_event);
|
|
1664
|
+
}
|
|
1665
|
+
OnRowDoubleClicked(_event) {
|
|
1202
1666
|
// Navigation is handled by mj-list-detail-grid
|
|
1203
1667
|
}
|
|
1668
|
+
/** @deprecated Use {@link OnRowDoubleClicked}. */
|
|
1669
|
+
onRowDoubleClicked(_event) {
|
|
1670
|
+
return this.OnRowDoubleClicked(_event);
|
|
1671
|
+
}
|
|
1672
|
+
OnSelectionChange(keys) {
|
|
1673
|
+
this.SelectedKeys = keys;
|
|
1674
|
+
}
|
|
1675
|
+
/** @deprecated Use {@link OnSelectionChange}. */
|
|
1204
1676
|
onSelectionChange(keys) {
|
|
1205
|
-
this.
|
|
1677
|
+
return this.OnSelectionChange(keys);
|
|
1678
|
+
}
|
|
1679
|
+
OnDataLoaded(event) {
|
|
1680
|
+
this.RowCount = event.totalCount;
|
|
1206
1681
|
}
|
|
1682
|
+
/** @deprecated Use {@link OnDataLoaded}. */
|
|
1207
1683
|
onDataLoaded(event) {
|
|
1208
|
-
this.
|
|
1684
|
+
return this.OnDataLoaded(event);
|
|
1209
1685
|
}
|
|
1210
|
-
|
|
1211
|
-
if (this.
|
|
1212
|
-
this.
|
|
1686
|
+
RefreshGrid() {
|
|
1687
|
+
if (this.ListDetailGrid) {
|
|
1688
|
+
this.ListDetailGrid.refresh();
|
|
1213
1689
|
}
|
|
1214
1690
|
}
|
|
1691
|
+
/** @deprecated Use {@link RefreshGrid}. */
|
|
1692
|
+
refreshGrid() {
|
|
1693
|
+
return this.RefreshGrid();
|
|
1694
|
+
}
|
|
1215
1695
|
// ==========================================
|
|
1216
1696
|
// Toolbar Actions
|
|
1217
1697
|
// ==========================================
|
|
1218
1698
|
// ==========================================
|
|
1219
1699
|
// Progress Percentage Getters
|
|
1220
1700
|
// ==========================================
|
|
1701
|
+
get RemoveProgressPercent() {
|
|
1702
|
+
return this.RemoveTotal > 0 ? Math.round((this.RemoveProgress / this.RemoveTotal) * 100) : 0;
|
|
1703
|
+
}
|
|
1704
|
+
/** @deprecated Use {@link RemoveProgressPercent}. */
|
|
1221
1705
|
get removeProgressPercent() {
|
|
1222
|
-
return this.
|
|
1706
|
+
return this.RemoveProgressPercent;
|
|
1707
|
+
}
|
|
1708
|
+
get AddProgressPercent() {
|
|
1709
|
+
return this.AddTotal > 0 ? Math.round((this.AddProgress / this.AddTotal) * 100) : 0;
|
|
1223
1710
|
}
|
|
1711
|
+
/** @deprecated Use {@link AddProgressPercent}. */
|
|
1224
1712
|
get addProgressPercent() {
|
|
1225
|
-
return this.
|
|
1713
|
+
return this.AddProgressPercent;
|
|
1226
1714
|
}
|
|
1227
1715
|
/** Update progress + recompute the stored percent atomically. Call this
|
|
1228
1716
|
* from inside the save loop so the bound value is set in lockstep with
|
|
1229
1717
|
* the underlying counter (avoids NG0100). */
|
|
1230
1718
|
setAddFromViewProgress(progress) {
|
|
1231
|
-
this.
|
|
1232
|
-
this.
|
|
1233
|
-
? Math.round((progress / this.
|
|
1719
|
+
this.AddFromViewProgress = progress;
|
|
1720
|
+
this.AddFromViewProgressPercent = this.AddFromViewTotal > 0
|
|
1721
|
+
? Math.round((progress / this.AddFromViewTotal) * 100)
|
|
1234
1722
|
: 0;
|
|
1235
1723
|
}
|
|
1724
|
+
OnRefreshClick() {
|
|
1725
|
+
this.RefreshGrid();
|
|
1726
|
+
}
|
|
1727
|
+
/** @deprecated Use {@link OnRefreshClick}. */
|
|
1236
1728
|
onRefreshClick() {
|
|
1237
|
-
this.
|
|
1729
|
+
return this.OnRefreshClick();
|
|
1730
|
+
}
|
|
1731
|
+
OnExportClick() {
|
|
1732
|
+
this.OpenExportDialog();
|
|
1238
1733
|
}
|
|
1734
|
+
/** @deprecated Use {@link OnExportClick}. */
|
|
1239
1735
|
onExportClick() {
|
|
1240
|
-
this.
|
|
1736
|
+
return this.OnExportClick();
|
|
1241
1737
|
}
|
|
1242
1738
|
/**
|
|
1243
1739
|
* Open the format + column picker (mockup 26). Resolves the candidate
|
|
@@ -1245,13 +1741,13 @@ export class SingleListDetailComponent extends BaseAngularComponent {
|
|
|
1245
1741
|
* RunView. Default selection is every non-virtual entity field, which
|
|
1246
1742
|
* matches what the underlying grid's "export all" path produced.
|
|
1247
1743
|
*/
|
|
1248
|
-
|
|
1249
|
-
if (!this.
|
|
1744
|
+
OpenExportDialog() {
|
|
1745
|
+
if (!this.ListRecord) {
|
|
1250
1746
|
this.sharedService.CreateSimpleNotification('Load a list first before exporting.', 'info', 3000);
|
|
1251
1747
|
return;
|
|
1252
1748
|
}
|
|
1253
1749
|
const md = this.ProviderToUse;
|
|
1254
|
-
const entityInfo = md.EntityByID(this.
|
|
1750
|
+
const entityInfo = md.EntityByID(this.ListRecord.EntityID);
|
|
1255
1751
|
if (!entityInfo) {
|
|
1256
1752
|
this.sharedService.CreateSimpleNotification(`Entity for this list not found in metadata.`, 'error', 4000);
|
|
1257
1753
|
return;
|
|
@@ -1260,32 +1756,52 @@ export class SingleListDetailComponent extends BaseAngularComponent {
|
|
|
1260
1756
|
this.sharedService.CreateSimpleNotification(`Composite-PK entities ('${entityInfo.Name}') aren't yet supported for List export.`, 'warning', 5000);
|
|
1261
1757
|
return;
|
|
1262
1758
|
}
|
|
1263
|
-
this.
|
|
1759
|
+
this.ExportFields = entityInfo.Fields
|
|
1264
1760
|
.filter((f) => f.IsVirtual !== true)
|
|
1265
1761
|
.map((f) => ({
|
|
1266
1762
|
Name: f.Name,
|
|
1267
1763
|
DisplayName: f.DisplayName || f.Name,
|
|
1268
1764
|
Selected: true,
|
|
1269
1765
|
}));
|
|
1270
|
-
this.
|
|
1271
|
-
this.
|
|
1272
|
-
this.
|
|
1766
|
+
this.ExportRecordCount = this.RowCount;
|
|
1767
|
+
this.ExportFormat = 'excel';
|
|
1768
|
+
this.ShowExportDialog = true;
|
|
1273
1769
|
this.cdr.detectChanges();
|
|
1274
1770
|
}
|
|
1275
|
-
|
|
1276
|
-
|
|
1771
|
+
/** @deprecated Use {@link OpenExportDialog}. */
|
|
1772
|
+
openExportDialog() {
|
|
1773
|
+
return this.OpenExportDialog();
|
|
1774
|
+
}
|
|
1775
|
+
CloseExportDialog() {
|
|
1776
|
+
this.ShowExportDialog = false;
|
|
1277
1777
|
this.cdr.detectChanges();
|
|
1278
1778
|
}
|
|
1279
|
-
|
|
1280
|
-
|
|
1779
|
+
/** @deprecated Use {@link CloseExportDialog}. */
|
|
1780
|
+
closeExportDialog() {
|
|
1781
|
+
return this.CloseExportDialog();
|
|
1782
|
+
}
|
|
1783
|
+
SelectAllExportFields() {
|
|
1784
|
+
for (const f of this.ExportFields)
|
|
1281
1785
|
f.Selected = true;
|
|
1282
1786
|
}
|
|
1283
|
-
|
|
1284
|
-
|
|
1787
|
+
/** @deprecated Use {@link SelectAllExportFields}. */
|
|
1788
|
+
selectAllExportFields() {
|
|
1789
|
+
return this.SelectAllExportFields();
|
|
1790
|
+
}
|
|
1791
|
+
SelectNoneExportFields() {
|
|
1792
|
+
for (const f of this.ExportFields)
|
|
1285
1793
|
f.Selected = false;
|
|
1286
1794
|
}
|
|
1795
|
+
/** @deprecated Use {@link SelectNoneExportFields}. */
|
|
1796
|
+
selectNoneExportFields() {
|
|
1797
|
+
return this.SelectNoneExportFields();
|
|
1798
|
+
}
|
|
1799
|
+
get SelectedExportFieldCount() {
|
|
1800
|
+
return this.ExportFields.filter((f) => f.Selected).length;
|
|
1801
|
+
}
|
|
1802
|
+
/** @deprecated Use {@link SelectedExportFieldCount}. */
|
|
1287
1803
|
get selectedExportFieldCount() {
|
|
1288
|
-
return this.
|
|
1804
|
+
return this.SelectedExportFieldCount;
|
|
1289
1805
|
}
|
|
1290
1806
|
/**
|
|
1291
1807
|
* Run the export with the user's chosen format + columns. Resolves
|
|
@@ -1294,27 +1810,27 @@ export class SingleListDetailComponent extends BaseAngularComponent {
|
|
|
1294
1810
|
* rows restricted to the chosen Fields. Output is projected to
|
|
1295
1811
|
* exactly the user's selected columns + ordering.
|
|
1296
1812
|
*/
|
|
1297
|
-
async
|
|
1298
|
-
if (!this.
|
|
1813
|
+
async ExecuteExport() {
|
|
1814
|
+
if (!this.ListRecord)
|
|
1299
1815
|
return;
|
|
1300
|
-
const selectedFields = this.
|
|
1816
|
+
const selectedFields = this.ExportFields.filter((f) => f.Selected).map((f) => f.Name);
|
|
1301
1817
|
if (selectedFields.length === 0)
|
|
1302
1818
|
return;
|
|
1303
|
-
this.
|
|
1819
|
+
this.IsExporting = true;
|
|
1304
1820
|
this.cdr.detectChanges();
|
|
1305
1821
|
try {
|
|
1306
1822
|
const md = this.ProviderToUse;
|
|
1307
|
-
const entityInfo = md.EntityByID(this.
|
|
1823
|
+
const entityInfo = md.EntityByID(this.ListRecord.EntityID);
|
|
1308
1824
|
// Cheap emptiness check before doing any row work
|
|
1309
1825
|
const rv = RunView.FromMetadataProvider(md);
|
|
1310
1826
|
const countResult = await rv.RunView({
|
|
1311
1827
|
EntityName: 'MJ: List Details',
|
|
1312
|
-
ExtraFilter: `ListID='${this.
|
|
1828
|
+
ExtraFilter: `ListID='${this.ListRecord.ID}'`,
|
|
1313
1829
|
ResultType: 'count_only',
|
|
1314
1830
|
});
|
|
1315
1831
|
if (countResult.Success && countResult.TotalRowCount === 0) {
|
|
1316
1832
|
this.sharedService.CreateSimpleNotification('List is empty — nothing to export.', 'info', 3000);
|
|
1317
|
-
this.
|
|
1833
|
+
this.ShowExportDialog = false;
|
|
1318
1834
|
return;
|
|
1319
1835
|
}
|
|
1320
1836
|
// Pull underlying entity rows restricted to the chosen fields, with
|
|
@@ -1324,7 +1840,7 @@ export class SingleListDetailComponent extends BaseAngularComponent {
|
|
|
1324
1840
|
const fieldsForQuery = Array.from(new Set([...entityInfo.PrimaryKeys.map((pk) => pk.Name), ...selectedFields]));
|
|
1325
1841
|
const rowResult = await rv.RunView({
|
|
1326
1842
|
EntityName: entityInfo.Name,
|
|
1327
|
-
ExtraFilter: await this.buildListMemberFilter(entityInfo, this.
|
|
1843
|
+
ExtraFilter: await this.buildListMemberFilter(entityInfo, this.ListRecord.ID, rv),
|
|
1328
1844
|
Fields: fieldsForQuery,
|
|
1329
1845
|
ResultType: 'simple',
|
|
1330
1846
|
});
|
|
@@ -1340,18 +1856,18 @@ export class SingleListDetailComponent extends BaseAngularComponent {
|
|
|
1340
1856
|
return projected;
|
|
1341
1857
|
});
|
|
1342
1858
|
const dateStamp = new Date().toISOString().slice(0, 10);
|
|
1343
|
-
const safeName = (this.
|
|
1344
|
-
const ext = this.
|
|
1859
|
+
const safeName = (this.ListRecord.Name || 'list').replace(/[^a-z0-9_-]+/gi, '_');
|
|
1860
|
+
const ext = this.ExportFormat === 'excel' ? 'xlsx' : this.ExportFormat;
|
|
1345
1861
|
const fileName = `${safeName}-${dateStamp}.${ext}`;
|
|
1346
|
-
const exportResult = this.
|
|
1862
|
+
const exportResult = this.ExportFormat === 'excel'
|
|
1347
1863
|
? await this.exportService.toExcel(rows, { fileName, includeHeaders: true })
|
|
1348
|
-
: this.
|
|
1864
|
+
: this.ExportFormat === 'csv'
|
|
1349
1865
|
? await this.exportService.toCSV(rows, { fileName, includeHeaders: true })
|
|
1350
1866
|
: await this.exportService.toJSON(rows, { fileName });
|
|
1351
1867
|
if (exportResult.success) {
|
|
1352
1868
|
this.exportService.downloadResult(exportResult);
|
|
1353
1869
|
this.sharedService.CreateSimpleNotification(`Exported ${rows.length} record(s)`, 'success', 3000);
|
|
1354
|
-
this.
|
|
1870
|
+
this.ShowExportDialog = false;
|
|
1355
1871
|
}
|
|
1356
1872
|
else {
|
|
1357
1873
|
this.sharedService.CreateSimpleNotification('Export failed', 'error', 5000);
|
|
@@ -1362,19 +1878,31 @@ export class SingleListDetailComponent extends BaseAngularComponent {
|
|
|
1362
1878
|
this.sharedService.CreateSimpleNotification(`Export error: ${message}`, 'error', 5000);
|
|
1363
1879
|
}
|
|
1364
1880
|
finally {
|
|
1365
|
-
this.
|
|
1881
|
+
this.IsExporting = false;
|
|
1366
1882
|
this.cdr.detectChanges();
|
|
1367
1883
|
}
|
|
1368
1884
|
}
|
|
1885
|
+
/** @deprecated Use {@link ExecuteExport}. */
|
|
1886
|
+
async executeExport() {
|
|
1887
|
+
return this.ExecuteExport();
|
|
1888
|
+
}
|
|
1889
|
+
ToggleAddDropdown() {
|
|
1890
|
+
this.ShowAddDropdown = !this.ShowAddDropdown;
|
|
1891
|
+
}
|
|
1892
|
+
/** @deprecated Use {@link ToggleAddDropdown}. */
|
|
1369
1893
|
toggleAddDropdown() {
|
|
1370
|
-
|
|
1894
|
+
return this.ToggleAddDropdown();
|
|
1371
1895
|
}
|
|
1372
|
-
|
|
1373
|
-
this.
|
|
1896
|
+
OnDropdownItemClick(item) {
|
|
1897
|
+
this.ShowAddDropdown = false;
|
|
1374
1898
|
if (item.Action) {
|
|
1375
1899
|
item.Action();
|
|
1376
1900
|
}
|
|
1377
1901
|
}
|
|
1902
|
+
/** @deprecated Use {@link OnDropdownItemClick}. */
|
|
1903
|
+
onDropdownItemClick(item) {
|
|
1904
|
+
return this.OnDropdownItemClick(item);
|
|
1905
|
+
}
|
|
1378
1906
|
// ==========================================
|
|
1379
1907
|
/**
|
|
1380
1908
|
* Predicate selecting the underlying entity rows that are members of a list. For a
|
|
@@ -1404,16 +1932,16 @@ export class SingleListDetailComponent extends BaseAngularComponent {
|
|
|
1404
1932
|
* the existing extract-record-id-from-composite-key logic to map
|
|
1405
1933
|
* `selectedKeys` to the actual `MJ: List Detail.RecordID` values.
|
|
1406
1934
|
*/
|
|
1407
|
-
async
|
|
1408
|
-
if (!this.
|
|
1935
|
+
async ApplyBulkStatus() {
|
|
1936
|
+
if (!this.ListRecord || this.SelectedKeys.length === 0 || !this.BulkStatus)
|
|
1409
1937
|
return;
|
|
1410
|
-
this.
|
|
1938
|
+
this.IsApplyingBulkStatus = true;
|
|
1411
1939
|
this.cdr.detectChanges();
|
|
1412
1940
|
try {
|
|
1413
1941
|
const md = this.ProviderToUse;
|
|
1414
1942
|
const rv = RunView.FromMetadataProvider(md);
|
|
1415
|
-
const entityInfo = md.EntityByID(this.
|
|
1416
|
-
const recordIds = this.
|
|
1943
|
+
const entityInfo = md.EntityByID(this.ListRecord.EntityID);
|
|
1944
|
+
const recordIds = this.SelectedKeys.map((key) => {
|
|
1417
1945
|
if (entityInfo && entityInfo.PrimaryKeys.length === 1) {
|
|
1418
1946
|
const ck = new CompositeKey();
|
|
1419
1947
|
ck.LoadFromConcatenatedString(key);
|
|
@@ -1421,7 +1949,7 @@ export class SingleListDetailComponent extends BaseAngularComponent {
|
|
|
1421
1949
|
}
|
|
1422
1950
|
return key;
|
|
1423
1951
|
});
|
|
1424
|
-
const filter = `ListID='${this.
|
|
1952
|
+
const filter = `ListID='${this.ListRecord.ID}' AND RecordID IN (${recordIds.map((id) => `'${String(id).replace(/'/g, "''")}'`).join(',')})`;
|
|
1425
1953
|
// Two-step: fetch just the IDs via a 'simple' RunView, then load each
|
|
1426
1954
|
// entity through GetEntityObject(..., CurrentUser) so the entity is
|
|
1427
1955
|
// born with the user context that Save() requires. RunView with
|
|
@@ -1451,7 +1979,7 @@ export class SingleListDetailComponent extends BaseAngularComponent {
|
|
|
1451
1979
|
failureMessages.push(reason);
|
|
1452
1980
|
continue;
|
|
1453
1981
|
}
|
|
1454
|
-
detail.Status = this.
|
|
1982
|
+
detail.Status = this.BulkStatus;
|
|
1455
1983
|
const ok = await detail.Save();
|
|
1456
1984
|
if (ok) {
|
|
1457
1985
|
updated++;
|
|
@@ -1465,14 +1993,14 @@ export class SingleListDetailComponent extends BaseAngularComponent {
|
|
|
1465
1993
|
}
|
|
1466
1994
|
}
|
|
1467
1995
|
this.sharedService.CreateSimpleNotification(failed === 0
|
|
1468
|
-
? `Updated ${updated} item(s) to '${this.
|
|
1996
|
+
? `Updated ${updated} item(s) to '${this.BulkStatus}'`
|
|
1469
1997
|
: `Updated ${updated}, ${failed} failed: ${failureMessages.join(' | ')}`, failed === 0 ? 'success' : 'warning', failed === 0 ? 3000 : 8000);
|
|
1470
|
-
this.
|
|
1998
|
+
this.BulkStatus = '';
|
|
1471
1999
|
// Defer the grid refresh to the next microtask — see comment in
|
|
1472
2000
|
// confirmRemoveFromList for why running it synchronously here
|
|
1473
2001
|
// triggers NG0100 and silently breaks the UI refresh.
|
|
1474
2002
|
await Promise.resolve();
|
|
1475
|
-
this.
|
|
2003
|
+
this.RefreshGrid();
|
|
1476
2004
|
this.bumpStatsRefresh();
|
|
1477
2005
|
}
|
|
1478
2006
|
catch (e) {
|
|
@@ -1480,10 +2008,14 @@ export class SingleListDetailComponent extends BaseAngularComponent {
|
|
|
1480
2008
|
this.sharedService.CreateSimpleNotification(`Bulk update failed: ${message}`, 'error', 5000);
|
|
1481
2009
|
}
|
|
1482
2010
|
finally {
|
|
1483
|
-
this.
|
|
2011
|
+
this.IsApplyingBulkStatus = false;
|
|
1484
2012
|
this.cdr.detectChanges();
|
|
1485
2013
|
}
|
|
1486
2014
|
}
|
|
2015
|
+
/** @deprecated Use {@link ApplyBulkStatus}. */
|
|
2016
|
+
async applyBulkStatus() {
|
|
2017
|
+
return this.ApplyBulkStatus();
|
|
2018
|
+
}
|
|
1487
2019
|
// ==========================================
|
|
1488
2020
|
// Bulk Move / Copy (mockups 23, 24)
|
|
1489
2021
|
//
|
|
@@ -1497,17 +2029,25 @@ export class SingleListDetailComponent extends BaseAngularComponent {
|
|
|
1497
2029
|
// needed for the preview. Apply step uses direct entity Save/Delete
|
|
1498
2030
|
// inside a single TransactionGroup (same pattern as Remove).
|
|
1499
2031
|
// ==========================================
|
|
1500
|
-
|
|
1501
|
-
|
|
2032
|
+
OpenMoveDialog() { this.openMoveCopyDialog('move'); }
|
|
2033
|
+
/** @deprecated Use {@link OpenMoveDialog}. */
|
|
2034
|
+
openMoveDialog() {
|
|
2035
|
+
return this.OpenMoveDialog();
|
|
2036
|
+
}
|
|
2037
|
+
OpenCopyDialog() { this.openMoveCopyDialog('copy'); }
|
|
2038
|
+
/** @deprecated Use {@link OpenCopyDialog}. */
|
|
2039
|
+
openCopyDialog() {
|
|
2040
|
+
return this.OpenCopyDialog();
|
|
2041
|
+
}
|
|
1502
2042
|
openMoveCopyDialog(mode) {
|
|
1503
|
-
if (!this.
|
|
2043
|
+
if (!this.ListRecord || this.SelectedKeys.length === 0) {
|
|
1504
2044
|
this.sharedService.CreateSimpleNotification('Please select records first', 'warning', 2500);
|
|
1505
2045
|
return;
|
|
1506
2046
|
}
|
|
1507
|
-
this.
|
|
1508
|
-
this.
|
|
1509
|
-
this.
|
|
1510
|
-
this.
|
|
2047
|
+
this.MoveCopyMode = mode;
|
|
2048
|
+
this.MoveCopySelectedTarget = null;
|
|
2049
|
+
this.MoveCopyTargetSearch = '';
|
|
2050
|
+
this.ShowMoveCopyDialog = true;
|
|
1511
2051
|
// Load candidates once per dialog open. We don't keep them in
|
|
1512
2052
|
// permanent component state because the user could create/delete
|
|
1513
2053
|
// lists between opens, and the target picker isn't visible often
|
|
@@ -1515,32 +2055,48 @@ export class SingleListDetailComponent extends BaseAngularComponent {
|
|
|
1515
2055
|
void this.loadMoveCopyTargets();
|
|
1516
2056
|
this.cdr.detectChanges();
|
|
1517
2057
|
}
|
|
1518
|
-
|
|
1519
|
-
this.
|
|
1520
|
-
this.
|
|
1521
|
-
this.
|
|
2058
|
+
CloseMoveCopyDialog() {
|
|
2059
|
+
this.ShowMoveCopyDialog = false;
|
|
2060
|
+
this.MoveCopyTargetCandidates = [];
|
|
2061
|
+
this.MoveCopySelectedTarget = null;
|
|
1522
2062
|
this.cdr.detectChanges();
|
|
1523
2063
|
}
|
|
2064
|
+
/** @deprecated Use {@link CloseMoveCopyDialog}. */
|
|
2065
|
+
closeMoveCopyDialog() {
|
|
2066
|
+
return this.CloseMoveCopyDialog();
|
|
2067
|
+
}
|
|
2068
|
+
SelectMoveCopyTarget(target) {
|
|
2069
|
+
this.MoveCopySelectedTarget = target;
|
|
2070
|
+
}
|
|
2071
|
+
/** @deprecated Use {@link SelectMoveCopyTarget}. */
|
|
1524
2072
|
selectMoveCopyTarget(target) {
|
|
1525
|
-
this.
|
|
2073
|
+
return this.SelectMoveCopyTarget(target);
|
|
1526
2074
|
}
|
|
1527
|
-
get
|
|
1528
|
-
const term = this.
|
|
2075
|
+
get FilteredMoveCopyTargets() {
|
|
2076
|
+
const term = this.MoveCopyTargetSearch.trim().toLowerCase();
|
|
1529
2077
|
if (!term)
|
|
1530
|
-
return this.
|
|
1531
|
-
return this.
|
|
2078
|
+
return this.MoveCopyTargetCandidates;
|
|
2079
|
+
return this.MoveCopyTargetCandidates.filter((l) => l.Name.toLowerCase().includes(term) ||
|
|
1532
2080
|
(l.Description?.toLowerCase().includes(term) ?? false));
|
|
1533
2081
|
}
|
|
2082
|
+
/** @deprecated Use {@link FilteredMoveCopyTargets}. */
|
|
2083
|
+
get filteredMoveCopyTargets() {
|
|
2084
|
+
return this.FilteredMoveCopyTargets;
|
|
2085
|
+
}
|
|
2086
|
+
get MoveCopyProgressPercent() {
|
|
2087
|
+
return this.MoveCopyTotal > 0 ? Math.round((this.MoveCopyProgress / this.MoveCopyTotal) * 100) : 0;
|
|
2088
|
+
}
|
|
2089
|
+
/** @deprecated Use {@link MoveCopyProgressPercent}. */
|
|
1534
2090
|
get moveCopyProgressPercent() {
|
|
1535
|
-
return this.
|
|
2091
|
+
return this.MoveCopyProgressPercent;
|
|
1536
2092
|
}
|
|
1537
2093
|
/** Load candidate target lists — same Entity, owned by or shared with
|
|
1538
2094
|
* the current user, excluding the list we're standing on. One RunView
|
|
1539
2095
|
* on open; results stay in memory until the dialog closes. */
|
|
1540
2096
|
async loadMoveCopyTargets() {
|
|
1541
|
-
if (!this.
|
|
2097
|
+
if (!this.ListRecord)
|
|
1542
2098
|
return;
|
|
1543
|
-
this.
|
|
2099
|
+
this.MoveCopyTargetCandidatesLoading = true;
|
|
1544
2100
|
this.cdr.detectChanges();
|
|
1545
2101
|
try {
|
|
1546
2102
|
const rv = RunView.FromMetadataProvider(this.ProviderToUse);
|
|
@@ -1548,7 +2104,7 @@ export class SingleListDetailComponent extends BaseAngularComponent {
|
|
|
1548
2104
|
// would over-restrict (Shared-With-Me lists should be valid targets
|
|
1549
2105
|
// when the user has Edit there); the server enforces the real
|
|
1550
2106
|
// permission on the per-row Save, so listing extra entries is fine.
|
|
1551
|
-
const filter = `EntityID='${this.
|
|
2107
|
+
const filter = `EntityID='${this.ListRecord.EntityID}' AND ID<>'${this.ListRecord.ID}'`;
|
|
1552
2108
|
const result = await rv.RunView({
|
|
1553
2109
|
EntityName: 'MJ: Lists',
|
|
1554
2110
|
ExtraFilter: filter,
|
|
@@ -1557,20 +2113,20 @@ export class SingleListDetailComponent extends BaseAngularComponent {
|
|
|
1557
2113
|
MaxRows: 500,
|
|
1558
2114
|
});
|
|
1559
2115
|
if (result.Success) {
|
|
1560
|
-
this.
|
|
2116
|
+
this.MoveCopyTargetCandidates = (result.Results ?? []);
|
|
1561
2117
|
}
|
|
1562
2118
|
else {
|
|
1563
|
-
this.
|
|
2119
|
+
this.MoveCopyTargetCandidates = [];
|
|
1564
2120
|
this.sharedService.CreateSimpleNotification(`Failed to load target lists: ${result.ErrorMessage}`, 'error', 4000);
|
|
1565
2121
|
}
|
|
1566
2122
|
}
|
|
1567
2123
|
catch (e) {
|
|
1568
|
-
this.
|
|
2124
|
+
this.MoveCopyTargetCandidates = [];
|
|
1569
2125
|
const message = e instanceof Error ? e.message : String(e);
|
|
1570
2126
|
this.sharedService.CreateSimpleNotification(`Failed to load target lists: ${message}`, 'error', 4000);
|
|
1571
2127
|
}
|
|
1572
2128
|
finally {
|
|
1573
|
-
this.
|
|
2129
|
+
this.MoveCopyTargetCandidatesLoading = false;
|
|
1574
2130
|
this.cdr.detectChanges();
|
|
1575
2131
|
}
|
|
1576
2132
|
}
|
|
@@ -1579,33 +2135,45 @@ export class SingleListDetailComponent extends BaseAngularComponent {
|
|
|
1579
2135
|
* immediately. For Move we build a local Delta and show the confirm
|
|
1580
2136
|
* dialog — the actual mutation fires from onMoveConfirmCommit().
|
|
1581
2137
|
*/
|
|
1582
|
-
async
|
|
1583
|
-
if (!this.
|
|
2138
|
+
async ConfirmMoveCopy() {
|
|
2139
|
+
if (!this.ListRecord || !this.MoveCopySelectedTarget || this.SelectedKeys.length === 0)
|
|
1584
2140
|
return;
|
|
1585
|
-
if (this.
|
|
2141
|
+
if (this.MoveCopyMode === 'copy') {
|
|
1586
2142
|
await this.applyMoveCopy(false);
|
|
1587
2143
|
return;
|
|
1588
2144
|
}
|
|
1589
2145
|
// Move: show delta-confirm so the user explicitly acks the source-side drop.
|
|
1590
2146
|
const recordIds = this.extractSelectedRecordIds();
|
|
1591
|
-
this.
|
|
1592
|
-
this.
|
|
1593
|
-
this.
|
|
2147
|
+
this.MoveDelta = this.buildMoveDelta(recordIds);
|
|
2148
|
+
this.ShowMoveCopyDialog = false;
|
|
2149
|
+
this.MoveDeltaConfirmVisible = true;
|
|
1594
2150
|
this.cdr.detectChanges();
|
|
1595
2151
|
}
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
this.
|
|
2152
|
+
/** @deprecated Use {@link ConfirmMoveCopy}. */
|
|
2153
|
+
async confirmMoveCopy() {
|
|
2154
|
+
return this.ConfirmMoveCopy();
|
|
2155
|
+
}
|
|
2156
|
+
OnMoveConfirmCancel() {
|
|
2157
|
+
this.MoveDeltaConfirmVisible = false;
|
|
2158
|
+
this.MoveDelta = null;
|
|
1599
2159
|
// Re-open the target picker so the user can change their mind without
|
|
1600
2160
|
// losing their selection.
|
|
1601
|
-
this.
|
|
2161
|
+
this.ShowMoveCopyDialog = true;
|
|
1602
2162
|
this.cdr.detectChanges();
|
|
1603
2163
|
}
|
|
1604
|
-
|
|
1605
|
-
|
|
2164
|
+
/** @deprecated Use {@link OnMoveConfirmCancel}. */
|
|
2165
|
+
onMoveConfirmCancel() {
|
|
2166
|
+
return this.OnMoveConfirmCancel();
|
|
2167
|
+
}
|
|
2168
|
+
async OnMoveConfirmCommit(_deltaToken) {
|
|
2169
|
+
this.MoveDeltaConfirmVisible = false;
|
|
1606
2170
|
this.cdr.detectChanges();
|
|
1607
2171
|
await this.applyMoveCopy(true);
|
|
1608
2172
|
}
|
|
2173
|
+
/** @deprecated Use {@link OnMoveConfirmCommit}. */
|
|
2174
|
+
async onMoveConfirmCommit(_deltaToken) {
|
|
2175
|
+
return this.OnMoveConfirmCommit(_deltaToken);
|
|
2176
|
+
}
|
|
1609
2177
|
/**
|
|
1610
2178
|
* Pull RecordIDs out of selectedKeys. selectedKeys arrive from the
|
|
1611
2179
|
* grid in concatenated-key format ("ID|<value>"). For single-PK entities
|
|
@@ -1615,8 +2183,8 @@ export class SingleListDetailComponent extends BaseAngularComponent {
|
|
|
1615
2183
|
*/
|
|
1616
2184
|
extractSelectedRecordIds() {
|
|
1617
2185
|
const md = this.ProviderToUse;
|
|
1618
|
-
const entityInfo = this.
|
|
1619
|
-
return this.
|
|
2186
|
+
const entityInfo = this.ListRecord ? md.EntityByID(this.ListRecord.EntityID) : null;
|
|
2187
|
+
return this.SelectedKeys.map((key) => {
|
|
1620
2188
|
if (entityInfo && entityInfo.PrimaryKeys.length === 1) {
|
|
1621
2189
|
const ck = new CompositeKey();
|
|
1622
2190
|
ck.LoadFromConcatenatedString(key);
|
|
@@ -1635,8 +2203,8 @@ export class SingleListDetailComponent extends BaseAngularComponent {
|
|
|
1635
2203
|
buildMoveDelta(recordIds) {
|
|
1636
2204
|
const removeCount = recordIds.length;
|
|
1637
2205
|
return {
|
|
1638
|
-
TargetListId: this.
|
|
1639
|
-
EntityName: this.
|
|
2206
|
+
TargetListId: this.ListRecord.ID,
|
|
2207
|
+
EntityName: this.ListRecord.Entity ?? '',
|
|
1640
2208
|
ToAdd: [],
|
|
1641
2209
|
ToRemove: recordIds,
|
|
1642
2210
|
Unchanged: [],
|
|
@@ -1645,11 +2213,11 @@ export class SingleListDetailComponent extends BaseAngularComponent {
|
|
|
1645
2213
|
Remove: removeCount,
|
|
1646
2214
|
Unchanged: 0,
|
|
1647
2215
|
SourceTotal: 0,
|
|
1648
|
-
TargetTotal: this.
|
|
2216
|
+
TargetTotal: this.RowCount,
|
|
1649
2217
|
},
|
|
1650
2218
|
Warnings: [{
|
|
1651
2219
|
Code: 'WILL_REMOVE_RECORDS',
|
|
1652
|
-
Message: `${removeCount} record(s) will be removed from "${this.
|
|
2220
|
+
Message: `${removeCount} record(s) will be removed from "${this.ListRecord.Name}" as part of the move`,
|
|
1653
2221
|
Details: { Count: removeCount },
|
|
1654
2222
|
}],
|
|
1655
2223
|
// Sentinel — client-side flow doesn't round-trip this.
|
|
@@ -1663,16 +2231,16 @@ export class SingleListDetailComponent extends BaseAngularComponent {
|
|
|
1663
2231
|
* leave records duplicated in both lists.
|
|
1664
2232
|
*/
|
|
1665
2233
|
async applyMoveCopy(isMove) {
|
|
1666
|
-
if (!this.
|
|
2234
|
+
if (!this.ListRecord || !this.MoveCopySelectedTarget)
|
|
1667
2235
|
return;
|
|
1668
|
-
const source = this.
|
|
1669
|
-
const target = this.
|
|
2236
|
+
const source = this.ListRecord;
|
|
2237
|
+
const target = this.MoveCopySelectedTarget;
|
|
1670
2238
|
const recordIds = this.extractSelectedRecordIds();
|
|
1671
2239
|
if (recordIds.length === 0)
|
|
1672
2240
|
return;
|
|
1673
|
-
this.
|
|
1674
|
-
this.
|
|
1675
|
-
this.
|
|
2241
|
+
this.IsApplyingMoveCopy = true;
|
|
2242
|
+
this.MoveCopyTotal = recordIds.length;
|
|
2243
|
+
this.MoveCopyProgress = 0;
|
|
1676
2244
|
this.cdr.detectChanges();
|
|
1677
2245
|
const md = this.ProviderToUse;
|
|
1678
2246
|
const rv = RunView.FromMetadataProvider(md);
|
|
@@ -1720,7 +2288,7 @@ export class SingleListDetailComponent extends BaseAngularComponent {
|
|
|
1720
2288
|
}
|
|
1721
2289
|
}
|
|
1722
2290
|
const ok = await tg.Submit();
|
|
1723
|
-
this.
|
|
2291
|
+
this.MoveCopyProgress = this.MoveCopyTotal;
|
|
1724
2292
|
if (!ok) {
|
|
1725
2293
|
this.sharedService.CreateSimpleNotification(`${isMove ? 'Move' : 'Copy'} partially failed — some changes may not have applied`, 'error', 5000);
|
|
1726
2294
|
return;
|
|
@@ -1733,11 +2301,11 @@ export class SingleListDetailComponent extends BaseAngularComponent {
|
|
|
1733
2301
|
// refresh by a microtask so the dialog/visibility flips happen in
|
|
1734
2302
|
// a fresh change-detection cycle.
|
|
1735
2303
|
await Promise.resolve();
|
|
1736
|
-
this.
|
|
1737
|
-
this.
|
|
2304
|
+
this.CloseMoveCopyDialog();
|
|
2305
|
+
this.MoveDelta = null;
|
|
1738
2306
|
if (isMove) {
|
|
1739
|
-
this.
|
|
1740
|
-
this.
|
|
2307
|
+
this.ListDetailGrid?.clearSelection();
|
|
2308
|
+
this.RefreshGrid();
|
|
1741
2309
|
this.bumpStatsRefresh();
|
|
1742
2310
|
}
|
|
1743
2311
|
this.cdr.detectChanges();
|
|
@@ -1748,40 +2316,48 @@ export class SingleListDetailComponent extends BaseAngularComponent {
|
|
|
1748
2316
|
this.sharedService.CreateSimpleNotification(`${isMove ? 'Move' : 'Copy'} failed: ${message}`, 'error', 5000);
|
|
1749
2317
|
}
|
|
1750
2318
|
finally {
|
|
1751
|
-
this.
|
|
2319
|
+
this.IsApplyingMoveCopy = false;
|
|
1752
2320
|
this.cdr.detectChanges();
|
|
1753
2321
|
}
|
|
1754
2322
|
}
|
|
1755
2323
|
// ==========================================
|
|
1756
2324
|
// Remove from List Dialog
|
|
1757
2325
|
// ==========================================
|
|
1758
|
-
|
|
1759
|
-
if (this.
|
|
2326
|
+
OpenRemoveDialog() {
|
|
2327
|
+
if (this.SelectedKeys.length === 0) {
|
|
1760
2328
|
this.sharedService.CreateSimpleNotification("Please select records to remove", 'warning', 2500);
|
|
1761
2329
|
return;
|
|
1762
2330
|
}
|
|
1763
|
-
this.
|
|
2331
|
+
this.ShowRemoveDialog = true;
|
|
1764
2332
|
}
|
|
2333
|
+
/** @deprecated Use {@link OpenRemoveDialog}. */
|
|
2334
|
+
openRemoveDialog() {
|
|
2335
|
+
return this.OpenRemoveDialog();
|
|
2336
|
+
}
|
|
2337
|
+
CloseRemoveDialog() {
|
|
2338
|
+
this.ShowRemoveDialog = false;
|
|
2339
|
+
this.IsRemoving = false;
|
|
2340
|
+
this.RemoveProgress = 0;
|
|
2341
|
+
this.RemoveTotal = 0;
|
|
2342
|
+
}
|
|
2343
|
+
/** @deprecated Use {@link CloseRemoveDialog}. */
|
|
1765
2344
|
closeRemoveDialog() {
|
|
1766
|
-
this.
|
|
1767
|
-
this.isRemoving = false;
|
|
1768
|
-
this.removeProgress = 0;
|
|
1769
|
-
this.removeTotal = 0;
|
|
2345
|
+
return this.CloseRemoveDialog();
|
|
1770
2346
|
}
|
|
1771
|
-
async
|
|
1772
|
-
if (!this.
|
|
2347
|
+
async ConfirmRemoveFromList() {
|
|
2348
|
+
if (!this.ListRecord || this.SelectedKeys.length === 0)
|
|
1773
2349
|
return;
|
|
1774
|
-
this.
|
|
1775
|
-
this.
|
|
1776
|
-
this.
|
|
2350
|
+
this.IsRemoving = true;
|
|
2351
|
+
this.RemoveTotal = this.SelectedKeys.length;
|
|
2352
|
+
this.RemoveProgress = 0;
|
|
1777
2353
|
const md = this.ProviderToUse;
|
|
1778
2354
|
const rv = RunView.FromMetadataProvider(this.ProviderToUse);
|
|
1779
|
-
const entityInfo = md.EntityByID(this.
|
|
2355
|
+
const entityInfo = md.EntityByID(this.ListRecord.EntityID);
|
|
1780
2356
|
// selectedKeys from grid are in concatenated format (ID|value)
|
|
1781
2357
|
// For single PK entities, RecordID in DB is just the raw value
|
|
1782
2358
|
// For composite PK entities, RecordID in DB is the concatenated format
|
|
1783
2359
|
// Extract the appropriate format for the query
|
|
1784
|
-
const selectedRecordIds = this.
|
|
2360
|
+
const selectedRecordIds = this.SelectedKeys.map(key => {
|
|
1785
2361
|
if (entityInfo && entityInfo.PrimaryKeys.length === 1) {
|
|
1786
2362
|
// Single PK: extract just the value from concatenated format
|
|
1787
2363
|
const compositeKey = new CompositeKey();
|
|
@@ -1793,7 +2369,7 @@ export class SingleListDetailComponent extends BaseAngularComponent {
|
|
|
1793
2369
|
return key;
|
|
1794
2370
|
}
|
|
1795
2371
|
});
|
|
1796
|
-
const listDetailsFilter = `ListID = '${this.
|
|
2372
|
+
const listDetailsFilter = `ListID = '${this.ListRecord.ID}' AND RecordID IN (${selectedRecordIds.map(id => `'${id}'`).join(',')})`;
|
|
1797
2373
|
const listDetailsResult = await rv.RunView({
|
|
1798
2374
|
EntityName: 'MJ: List Details',
|
|
1799
2375
|
ExtraFilter: listDetailsFilter,
|
|
@@ -1802,7 +2378,7 @@ export class SingleListDetailComponent extends BaseAngularComponent {
|
|
|
1802
2378
|
if (!listDetailsResult.Success) {
|
|
1803
2379
|
LogError("Error loading list details for removal", undefined, listDetailsResult.ErrorMessage);
|
|
1804
2380
|
this.sharedService.CreateSimpleNotification("Failed to remove records", 'error', 2500);
|
|
1805
|
-
this.
|
|
2381
|
+
this.IsRemoving = false;
|
|
1806
2382
|
return;
|
|
1807
2383
|
}
|
|
1808
2384
|
// Use transaction group for bulk delete
|
|
@@ -1814,7 +2390,7 @@ export class SingleListDetailComponent extends BaseAngularComponent {
|
|
|
1814
2390
|
}
|
|
1815
2391
|
const success = await tg.Submit();
|
|
1816
2392
|
if (success) {
|
|
1817
|
-
this.
|
|
2393
|
+
this.RemoveProgress = this.RemoveTotal;
|
|
1818
2394
|
this.sharedService.CreateSimpleNotification(`Removed ${listDetails.length} record${listDetails.length !== 1 ? 's' : ''} from list`, 'success', 2500);
|
|
1819
2395
|
// Defer cleanup + refresh to the next microtask. Running these
|
|
1820
2396
|
// synchronously inside the current change-detection cycle
|
|
@@ -1823,79 +2399,99 @@ export class SingleListDetailComponent extends BaseAngularComponent {
|
|
|
1823
2399
|
// stale state — the deletion succeeds server-side but the UI
|
|
1824
2400
|
// doesn't re-fetch until the user navigates away and back.
|
|
1825
2401
|
await Promise.resolve();
|
|
1826
|
-
this.
|
|
1827
|
-
this.
|
|
1828
|
-
this.
|
|
2402
|
+
this.CloseRemoveDialog();
|
|
2403
|
+
this.ListDetailGrid?.clearSelection();
|
|
2404
|
+
this.RefreshGrid();
|
|
1829
2405
|
this.bumpStatsRefresh();
|
|
1830
2406
|
this.cdr.detectChanges();
|
|
1831
2407
|
}
|
|
1832
2408
|
else {
|
|
1833
2409
|
LogError("Error removing records from list");
|
|
1834
2410
|
this.sharedService.CreateSimpleNotification("Failed to remove some records", 'error', 2500);
|
|
1835
|
-
this.
|
|
2411
|
+
this.IsRemoving = false;
|
|
1836
2412
|
}
|
|
1837
2413
|
}
|
|
2414
|
+
/** @deprecated Use {@link ConfirmRemoveFromList}. */
|
|
2415
|
+
async confirmRemoveFromList() {
|
|
2416
|
+
return this.ConfirmRemoveFromList();
|
|
2417
|
+
}
|
|
1838
2418
|
// ==========================================
|
|
1839
2419
|
// Add Records Dialog
|
|
1840
2420
|
// ==========================================
|
|
1841
|
-
async
|
|
1842
|
-
this.
|
|
1843
|
-
this.
|
|
1844
|
-
this.
|
|
1845
|
-
this.
|
|
1846
|
-
this.
|
|
2421
|
+
async OpenAddRecordsDialog() {
|
|
2422
|
+
this.ShowAddRecordsDialog = true;
|
|
2423
|
+
this.AddableRecords = [];
|
|
2424
|
+
this.AddRecordsSearchFilter = "";
|
|
2425
|
+
this.AddDialogLoading = true;
|
|
2426
|
+
this.AddDialogSaving = false;
|
|
1847
2427
|
// Load existing list detail IDs to mark which records are already in the list
|
|
1848
2428
|
await this.loadExistingListDetailIds();
|
|
1849
|
-
this.
|
|
2429
|
+
this.AddDialogLoading = false;
|
|
1850
2430
|
// Explicit CD: the GraphQL promise resolution doesn't reliably produce an
|
|
1851
2431
|
// Angular tick, so without this the spinner stays up until the next user
|
|
1852
2432
|
// event (click/keystroke) forces a change-detection cycle.
|
|
1853
2433
|
this.cdr.detectChanges();
|
|
1854
2434
|
}
|
|
2435
|
+
/** @deprecated Use {@link OpenAddRecordsDialog}. */
|
|
2436
|
+
async openAddRecordsDialog() {
|
|
2437
|
+
return this.OpenAddRecordsDialog();
|
|
2438
|
+
}
|
|
2439
|
+
CloseAddRecordsDialog() {
|
|
2440
|
+
this.ShowAddRecordsDialog = false;
|
|
2441
|
+
this.AddableRecords = [];
|
|
2442
|
+
this.AddRecordsSearchFilter = "";
|
|
2443
|
+
this.ExistingListDetailIds.clear();
|
|
2444
|
+
this.AddDialogSaving = false;
|
|
2445
|
+
this.AddProgress = 0;
|
|
2446
|
+
this.AddTotal = 0;
|
|
2447
|
+
}
|
|
2448
|
+
/** @deprecated Use {@link CloseAddRecordsDialog}. */
|
|
1855
2449
|
closeAddRecordsDialog() {
|
|
1856
|
-
this.
|
|
1857
|
-
this.addableRecords = [];
|
|
1858
|
-
this.addRecordsSearchFilter = "";
|
|
1859
|
-
this.existingListDetailIds.clear();
|
|
1860
|
-
this.addDialogSaving = false;
|
|
1861
|
-
this.addProgress = 0;
|
|
1862
|
-
this.addTotal = 0;
|
|
2450
|
+
return this.CloseAddRecordsDialog();
|
|
1863
2451
|
}
|
|
1864
2452
|
async loadExistingListDetailIds() {
|
|
1865
|
-
if (!this.
|
|
2453
|
+
if (!this.ListRecord)
|
|
1866
2454
|
return;
|
|
1867
2455
|
const md = this.ProviderToUse;
|
|
1868
2456
|
const rv = RunView.FromMetadataProvider(this.ProviderToUse);
|
|
1869
2457
|
const result = await rv.RunView({
|
|
1870
2458
|
EntityName: 'MJ: List Details',
|
|
1871
|
-
ExtraFilter: `ListID = '${this.
|
|
2459
|
+
ExtraFilter: `ListID = '${this.ListRecord.ID}'`,
|
|
1872
2460
|
Fields: ['RecordID'],
|
|
1873
2461
|
ResultType: 'simple'
|
|
1874
2462
|
}, md.CurrentUser);
|
|
1875
2463
|
if (result.Success) {
|
|
1876
|
-
this.
|
|
2464
|
+
this.ExistingListDetailIds = new Set(result.Results.map(r => NormalizeUUID(r.RecordID)));
|
|
1877
2465
|
}
|
|
1878
2466
|
}
|
|
1879
|
-
|
|
2467
|
+
OnAddRecordsSearchInputEvent(event) {
|
|
1880
2468
|
const value = event.target.value;
|
|
1881
|
-
this.
|
|
2469
|
+
this.OnAddRecordsSearchChange(value);
|
|
1882
2470
|
}
|
|
1883
|
-
|
|
1884
|
-
|
|
2471
|
+
/** @deprecated Use {@link OnAddRecordsSearchInputEvent}. */
|
|
2472
|
+
onAddRecordsSearchInputEvent(event) {
|
|
2473
|
+
return this.OnAddRecordsSearchInputEvent(event);
|
|
2474
|
+
}
|
|
2475
|
+
OnAddRecordsSearchChange(value) {
|
|
2476
|
+
this.AddRecordsSearchFilter = value;
|
|
1885
2477
|
this.searchSubject.next(value);
|
|
1886
2478
|
}
|
|
2479
|
+
/** @deprecated Use {@link OnAddRecordsSearchChange}. */
|
|
2480
|
+
onAddRecordsSearchChange(value) {
|
|
2481
|
+
return this.OnAddRecordsSearchChange(value);
|
|
2482
|
+
}
|
|
1887
2483
|
async searchRecords(searchText) {
|
|
1888
|
-
if (!this.
|
|
1889
|
-
this.
|
|
2484
|
+
if (!this.ListRecord || !searchText || searchText.length < 2) {
|
|
2485
|
+
this.AddableRecords = [];
|
|
1890
2486
|
this.cdr.detectChanges();
|
|
1891
2487
|
return;
|
|
1892
2488
|
}
|
|
1893
|
-
this.
|
|
2489
|
+
this.AddDialogLoading = true;
|
|
1894
2490
|
this.cdr.detectChanges();
|
|
1895
2491
|
const md = this.ProviderToUse;
|
|
1896
|
-
const sourceEntityInfo = md.EntityByID(this.
|
|
2492
|
+
const sourceEntityInfo = md.EntityByID(this.ListRecord.EntityID);
|
|
1897
2493
|
if (!sourceEntityInfo) {
|
|
1898
|
-
this.
|
|
2494
|
+
this.AddDialogLoading = false;
|
|
1899
2495
|
this.cdr.detectChanges();
|
|
1900
2496
|
return;
|
|
1901
2497
|
}
|
|
@@ -1909,13 +2505,13 @@ export class SingleListDetailComponent extends BaseAngularComponent {
|
|
|
1909
2505
|
}
|
|
1910
2506
|
const rv = RunView.FromMetadataProvider(this.ProviderToUse);
|
|
1911
2507
|
const result = await rv.RunView({
|
|
1912
|
-
EntityName: this.
|
|
2508
|
+
EntityName: this.ListRecord.Entity,
|
|
1913
2509
|
ExtraFilter: filter,
|
|
1914
2510
|
MaxRows: 100,
|
|
1915
2511
|
ResultType: 'simple'
|
|
1916
2512
|
});
|
|
1917
2513
|
if (result.Success) {
|
|
1918
|
-
this.
|
|
2514
|
+
this.AddableRecords = result.Results.map((record) => {
|
|
1919
2515
|
// Compact key segment (raw value, or "F1|v1||F2|v2" for a composite key) — the form ListDetail.RecordID stores
|
|
1920
2516
|
const recordId = CompositeKey.FromEntityRecord(sourceEntityInfo, record).ToCompactURLSegment();
|
|
1921
2517
|
return {
|
|
@@ -1923,39 +2519,55 @@ export class SingleListDetailComponent extends BaseAngularComponent {
|
|
|
1923
2519
|
Name: displayField.Field
|
|
1924
2520
|
? FormatRecordDisplayValue(recordId, record[displayField.Field.Name], displayField)
|
|
1925
2521
|
: recordId,
|
|
1926
|
-
isInList: this.
|
|
2522
|
+
isInList: this.ExistingListDetailIds.has(NormalizeUUID(recordId)),
|
|
1927
2523
|
isSelected: false
|
|
1928
2524
|
};
|
|
1929
2525
|
});
|
|
1930
2526
|
}
|
|
1931
|
-
this.
|
|
2527
|
+
this.AddDialogLoading = false;
|
|
1932
2528
|
this.cdr.detectChanges();
|
|
1933
2529
|
}
|
|
1934
|
-
|
|
2530
|
+
ToggleRecordSelection(record) {
|
|
1935
2531
|
if (record.isInList)
|
|
1936
2532
|
return; // Can't select records already in list
|
|
1937
2533
|
record.isSelected = !record.isSelected;
|
|
1938
2534
|
}
|
|
2535
|
+
/** @deprecated Use {@link ToggleRecordSelection}. */
|
|
2536
|
+
toggleRecordSelection(record) {
|
|
2537
|
+
return this.ToggleRecordSelection(record);
|
|
2538
|
+
}
|
|
2539
|
+
get SelectedAddableRecords() {
|
|
2540
|
+
return this.AddableRecords.filter(r => r.isSelected);
|
|
2541
|
+
}
|
|
2542
|
+
/** @deprecated Use {@link SelectedAddableRecords}. */
|
|
1939
2543
|
get selectedAddableRecords() {
|
|
1940
|
-
return this.
|
|
2544
|
+
return this.SelectedAddableRecords;
|
|
1941
2545
|
}
|
|
1942
|
-
|
|
1943
|
-
this.
|
|
2546
|
+
SelectAllAddable() {
|
|
2547
|
+
this.AddableRecords.forEach(r => {
|
|
1944
2548
|
if (!r.isInList)
|
|
1945
2549
|
r.isSelected = true;
|
|
1946
2550
|
});
|
|
1947
2551
|
}
|
|
2552
|
+
/** @deprecated Use {@link SelectAllAddable}. */
|
|
2553
|
+
selectAllAddable() {
|
|
2554
|
+
return this.SelectAllAddable();
|
|
2555
|
+
}
|
|
2556
|
+
DeselectAllAddable() {
|
|
2557
|
+
this.AddableRecords.forEach(r => r.isSelected = false);
|
|
2558
|
+
}
|
|
2559
|
+
/** @deprecated Use {@link DeselectAllAddable}. */
|
|
1948
2560
|
deselectAllAddable() {
|
|
1949
|
-
this.
|
|
2561
|
+
return this.DeselectAllAddable();
|
|
1950
2562
|
}
|
|
1951
|
-
async
|
|
1952
|
-
const recordsToAdd = this.
|
|
1953
|
-
if (recordsToAdd.length === 0 || !this.
|
|
2563
|
+
async ConfirmAddRecords() {
|
|
2564
|
+
const recordsToAdd = this.SelectedAddableRecords;
|
|
2565
|
+
if (recordsToAdd.length === 0 || !this.ListRecord)
|
|
1954
2566
|
return;
|
|
1955
|
-
this.
|
|
2567
|
+
this.AddDialogSaving = true;
|
|
1956
2568
|
// Reserve 20% of progress for tg.Submit()
|
|
1957
|
-
this.
|
|
1958
|
-
this.
|
|
2569
|
+
this.AddTotal = recordsToAdd.length;
|
|
2570
|
+
this.AddProgress = 0;
|
|
1959
2571
|
const progressPerRecord = 0.8 / recordsToAdd.length; // 80% for individual saves
|
|
1960
2572
|
const md = this.ProviderToUse;
|
|
1961
2573
|
// Use transaction group for bulk insert
|
|
@@ -1963,7 +2575,7 @@ export class SingleListDetailComponent extends BaseAngularComponent {
|
|
|
1963
2575
|
for (let i = 0; i < recordsToAdd.length; i++) {
|
|
1964
2576
|
const record = recordsToAdd[i];
|
|
1965
2577
|
const listDetail = await md.GetEntityObject("MJ: List Details", md.CurrentUser);
|
|
1966
|
-
listDetail.ListID = this.
|
|
2578
|
+
listDetail.ListID = this.ListRecord.ID;
|
|
1967
2579
|
listDetail.RecordID = record.ID;
|
|
1968
2580
|
listDetail.TransactionGroup = tg;
|
|
1969
2581
|
const result = await listDetail.Save();
|
|
@@ -1973,86 +2585,106 @@ export class SingleListDetailComponent extends BaseAngularComponent {
|
|
|
1973
2585
|
});
|
|
1974
2586
|
}
|
|
1975
2587
|
// Update progress (0-80%)
|
|
1976
|
-
this.
|
|
2588
|
+
this.AddProgress = Math.round((i + 1) * progressPerRecord * this.AddTotal);
|
|
1977
2589
|
}
|
|
1978
2590
|
// Show 80% complete before submit
|
|
1979
|
-
this.
|
|
2591
|
+
this.AddProgress = Math.round(this.AddTotal * 0.8);
|
|
1980
2592
|
const success = await tg.Submit();
|
|
1981
2593
|
if (success) {
|
|
1982
|
-
this.
|
|
2594
|
+
this.AddProgress = this.AddTotal;
|
|
1983
2595
|
this.sharedService.CreateSimpleNotification(`Added ${recordsToAdd.length} record${recordsToAdd.length !== 1 ? 's' : ''} to list`, 'success', 2500);
|
|
1984
2596
|
// Defer dialog close + grid refresh to the next microtask. See
|
|
1985
2597
|
// confirmRemoveFromList for the NG0100 background.
|
|
1986
2598
|
await Promise.resolve();
|
|
1987
|
-
this.
|
|
1988
|
-
this.
|
|
2599
|
+
this.CloseAddRecordsDialog();
|
|
2600
|
+
this.RefreshGrid();
|
|
1989
2601
|
this.bumpStatsRefresh();
|
|
1990
2602
|
this.cdr.detectChanges();
|
|
1991
2603
|
}
|
|
1992
2604
|
else {
|
|
1993
2605
|
LogError("Error adding records to list");
|
|
1994
2606
|
this.sharedService.CreateSimpleNotification("Failed to add some records", 'error', 2500);
|
|
1995
|
-
this.
|
|
2607
|
+
this.AddDialogSaving = false;
|
|
1996
2608
|
}
|
|
1997
2609
|
}
|
|
2610
|
+
/** @deprecated Use {@link ConfirmAddRecords}. */
|
|
2611
|
+
async confirmAddRecords() {
|
|
2612
|
+
return this.ConfirmAddRecords();
|
|
2613
|
+
}
|
|
1998
2614
|
// ==========================================
|
|
1999
2615
|
// Add From View Dialog (existing functionality, cleaned up)
|
|
2000
2616
|
// ==========================================
|
|
2001
|
-
async
|
|
2002
|
-
this.
|
|
2003
|
-
this.
|
|
2617
|
+
async OpenAddFromViewDialog() {
|
|
2618
|
+
this.ShowAddFromViewDialog = true;
|
|
2619
|
+
this.UserViewsToAdd = [];
|
|
2004
2620
|
this.userViewsToAddIds.clear();
|
|
2005
|
-
if (!this.
|
|
2621
|
+
if (!this.UserViews) {
|
|
2006
2622
|
await this.loadEntityViews();
|
|
2007
2623
|
}
|
|
2008
2624
|
}
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
this.
|
|
2625
|
+
/** @deprecated Use {@link OpenAddFromViewDialog}. */
|
|
2626
|
+
async openAddFromViewDialog() {
|
|
2627
|
+
return this.OpenAddFromViewDialog();
|
|
2628
|
+
}
|
|
2629
|
+
CloseAddFromViewDialog() {
|
|
2630
|
+
this.ShowAddFromViewDialog = false;
|
|
2631
|
+
this.UserViewsToAdd = [];
|
|
2012
2632
|
this.userViewsToAddIds.clear();
|
|
2013
|
-
this.
|
|
2014
|
-
this.
|
|
2633
|
+
this.ShowAddFromViewLoader = false;
|
|
2634
|
+
this.AddFromViewTotal = 0;
|
|
2015
2635
|
this.setAddFromViewProgress(0);
|
|
2016
2636
|
}
|
|
2637
|
+
/** @deprecated Use {@link CloseAddFromViewDialog}. */
|
|
2638
|
+
closeAddFromViewDialog() {
|
|
2639
|
+
return this.CloseAddFromViewDialog();
|
|
2640
|
+
}
|
|
2017
2641
|
async loadEntityViews() {
|
|
2018
|
-
if (!this.
|
|
2642
|
+
if (!this.ListRecord || !this.ListRecord.Entity)
|
|
2019
2643
|
return;
|
|
2020
|
-
this.
|
|
2644
|
+
this.ShowAddFromViewLoader = true;
|
|
2021
2645
|
const rv = RunView.FromMetadataProvider(this.ProviderToUse);
|
|
2022
2646
|
const md = this.ProviderToUse;
|
|
2023
2647
|
const runViewResult = await rv.RunView({
|
|
2024
2648
|
EntityName: "MJ: User Views",
|
|
2025
|
-
ExtraFilter: `UserID = '${md.CurrentUser.ID}' AND EntityID = '${this.
|
|
2649
|
+
ExtraFilter: `UserID = '${md.CurrentUser.ID}' AND EntityID = '${this.ListRecord.EntityID}'`,
|
|
2026
2650
|
ResultType: 'entity_object'
|
|
2027
2651
|
}, md.CurrentUser);
|
|
2028
2652
|
if (!runViewResult.Success) {
|
|
2029
|
-
LogError(`Error loading User Views for entity ${this.
|
|
2653
|
+
LogError(`Error loading User Views for entity ${this.ListRecord.Entity}`);
|
|
2030
2654
|
}
|
|
2031
2655
|
else {
|
|
2032
|
-
this.
|
|
2656
|
+
this.UserViews = runViewResult.Results;
|
|
2033
2657
|
}
|
|
2034
|
-
this.
|
|
2658
|
+
this.ShowAddFromViewLoader = false;
|
|
2035
2659
|
this.cdr.detectChanges();
|
|
2036
2660
|
}
|
|
2037
|
-
|
|
2038
|
-
const index = this.
|
|
2661
|
+
ToggleViewSelection(view) {
|
|
2662
|
+
const index = this.UserViewsToAdd.findIndex(v => UUIDsEqual(v.ID, view.ID));
|
|
2039
2663
|
if (index >= 0) {
|
|
2040
|
-
this.
|
|
2664
|
+
this.UserViewsToAdd.splice(index, 1);
|
|
2041
2665
|
this.userViewsToAddIds.delete(NormalizeUUID(view.ID));
|
|
2042
2666
|
}
|
|
2043
2667
|
else {
|
|
2044
|
-
this.
|
|
2668
|
+
this.UserViewsToAdd.push(view);
|
|
2045
2669
|
this.userViewsToAddIds.add(NormalizeUUID(view.ID));
|
|
2046
2670
|
}
|
|
2047
2671
|
}
|
|
2048
|
-
|
|
2672
|
+
/** @deprecated Use {@link ToggleViewSelection}. */
|
|
2673
|
+
toggleViewSelection(view) {
|
|
2674
|
+
return this.ToggleViewSelection(view);
|
|
2675
|
+
}
|
|
2676
|
+
IsViewSelected(view) {
|
|
2049
2677
|
return this.userViewsToAddIds.has(NormalizeUUID(view.ID));
|
|
2050
2678
|
}
|
|
2051
|
-
|
|
2052
|
-
|
|
2679
|
+
/** @deprecated Use {@link IsViewSelected}. */
|
|
2680
|
+
isViewSelected(view) {
|
|
2681
|
+
return this.IsViewSelected(view);
|
|
2682
|
+
}
|
|
2683
|
+
async ConfirmAddFromView() {
|
|
2684
|
+
if (!this.ListRecord || this.UserViewsToAdd.length === 0)
|
|
2053
2685
|
return;
|
|
2054
|
-
this.
|
|
2055
|
-
this.
|
|
2686
|
+
this.ShowAddFromViewLoader = true;
|
|
2687
|
+
this.FetchingRecordsToSave = true;
|
|
2056
2688
|
this.cdr.detectChanges();
|
|
2057
2689
|
const rv = RunView.FromMetadataProvider(this.ProviderToUse);
|
|
2058
2690
|
const md = this.ProviderToUse;
|
|
@@ -2061,7 +2693,7 @@ export class SingleListDetailComponent extends BaseAngularComponent {
|
|
|
2061
2693
|
// sequential RunView per view (N round-trips → 1). Each view has a distinct ViewID,
|
|
2062
2694
|
// so this can't collapse to one query, but RunViews parallelizes the round trips.
|
|
2063
2695
|
const recordIdSet = new Set();
|
|
2064
|
-
const viewResults = await rv.RunViews(this.
|
|
2696
|
+
const viewResults = await rv.RunViews(this.UserViewsToAdd.map(userView => ({
|
|
2065
2697
|
ViewID: userView.ID,
|
|
2066
2698
|
ViewEntity: userView,
|
|
2067
2699
|
Fields: ["ID"]
|
|
@@ -2074,15 +2706,15 @@ export class SingleListDetailComponent extends BaseAngularComponent {
|
|
|
2074
2706
|
}
|
|
2075
2707
|
// Filter out records already in the list
|
|
2076
2708
|
await this.loadExistingListDetailIds();
|
|
2077
|
-
const recordsToAdd = [...recordIdSet].filter(id => !this.
|
|
2078
|
-
this.
|
|
2709
|
+
const recordsToAdd = [...recordIdSet].filter(id => !this.ExistingListDetailIds.has(id));
|
|
2710
|
+
this.AddFromViewTotal = recordsToAdd.length;
|
|
2079
2711
|
this.setAddFromViewProgress(0);
|
|
2080
|
-
this.
|
|
2712
|
+
this.FetchingRecordsToSave = false;
|
|
2081
2713
|
this.cdr.detectChanges();
|
|
2082
2714
|
const progressPerRecord = 0.8 / Math.max(recordsToAdd.length, 1); // 80% for individual saves
|
|
2083
2715
|
if (recordsToAdd.length === 0) {
|
|
2084
2716
|
this.sharedService.CreateSimpleNotification("All records already in list", 'info', 2500);
|
|
2085
|
-
this.
|
|
2717
|
+
this.ShowAddFromViewLoader = false;
|
|
2086
2718
|
this.cdr.detectChanges();
|
|
2087
2719
|
return;
|
|
2088
2720
|
}
|
|
@@ -2092,7 +2724,7 @@ export class SingleListDetailComponent extends BaseAngularComponent {
|
|
|
2092
2724
|
for (let i = 0; i < recordsToAdd.length; i++) {
|
|
2093
2725
|
const recordID = recordsToAdd[i];
|
|
2094
2726
|
const listDetail = await md.GetEntityObject("MJ: List Details", md.CurrentUser);
|
|
2095
|
-
listDetail.ListID = this.
|
|
2727
|
+
listDetail.ListID = this.ListRecord.ID;
|
|
2096
2728
|
listDetail.RecordID = recordID;
|
|
2097
2729
|
listDetail.TransactionGroup = tg;
|
|
2098
2730
|
const result = await listDetail.Save();
|
|
@@ -2106,36 +2738,40 @@ export class SingleListDetailComponent extends BaseAngularComponent {
|
|
|
2106
2738
|
// CD cycle on this iteration. Without detectChanges() the next await
|
|
2107
2739
|
// can yield in the middle of a cycle and Angular's dev-mode re-check
|
|
2108
2740
|
// catches the percent changing → NG0100 flood.
|
|
2109
|
-
this.setAddFromViewProgress(Math.round((i + 1) * progressPerRecord * this.
|
|
2741
|
+
this.setAddFromViewProgress(Math.round((i + 1) * progressPerRecord * this.AddFromViewTotal));
|
|
2110
2742
|
this.cdr.detectChanges();
|
|
2111
2743
|
}
|
|
2112
2744
|
// Show 80% complete before submit
|
|
2113
|
-
this.setAddFromViewProgress(Math.round(this.
|
|
2745
|
+
this.setAddFromViewProgress(Math.round(this.AddFromViewTotal * 0.8));
|
|
2114
2746
|
this.cdr.detectChanges();
|
|
2115
2747
|
const success = await tg.Submit();
|
|
2116
2748
|
if (success) {
|
|
2117
|
-
this.setAddFromViewProgress(this.
|
|
2749
|
+
this.setAddFromViewProgress(this.AddFromViewTotal);
|
|
2118
2750
|
this.sharedService.CreateSimpleNotification(`Added ${recordsToAdd.length} record${recordsToAdd.length !== 1 ? 's' : ''} to list`, 'success', 2500);
|
|
2119
2751
|
// Defer dialog close + grid refresh to the next microtask. See
|
|
2120
2752
|
// confirmRemoveFromList for the NG0100 background.
|
|
2121
2753
|
await Promise.resolve();
|
|
2122
|
-
this.
|
|
2123
|
-
this.
|
|
2754
|
+
this.CloseAddFromViewDialog();
|
|
2755
|
+
this.RefreshGrid();
|
|
2124
2756
|
this.bumpStatsRefresh();
|
|
2125
2757
|
this.cdr.detectChanges();
|
|
2126
2758
|
}
|
|
2127
2759
|
else {
|
|
2128
2760
|
LogError("Error adding records from view to list");
|
|
2129
2761
|
this.sharedService.CreateSimpleNotification("Failed to add some records", 'error', 2500);
|
|
2130
|
-
this.
|
|
2762
|
+
this.ShowAddFromViewLoader = false;
|
|
2131
2763
|
}
|
|
2132
2764
|
}
|
|
2765
|
+
/** @deprecated Use {@link ConfirmAddFromView}. */
|
|
2766
|
+
async confirmAddFromView() {
|
|
2767
|
+
return this.ConfirmAddFromView();
|
|
2768
|
+
}
|
|
2133
2769
|
static ɵfac = function SingleListDetailComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || SingleListDetailComponent)(i0.ɵɵdirectiveInject(i1.SharedService), i0.ɵɵdirectiveInject(i0.ChangeDetectorRef), i0.ɵɵdirectiveInject(i0.ElementRef), i0.ɵɵdirectiveInject(i2.ExportService), i0.ɵɵdirectiveInject(i3.ListSharingService)); };
|
|
2134
2770
|
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: SingleListDetailComponent, selectors: [["mj-list-detail"]], viewQuery: function SingleListDetailComponent_Query(rf, ctx) { if (rf & 1) {
|
|
2135
2771
|
i0.ɵɵviewQuery(_c0, 5);
|
|
2136
2772
|
} if (rf & 2) {
|
|
2137
2773
|
let _t;
|
|
2138
|
-
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.
|
|
2774
|
+
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.ListDetailGrid = _t.first);
|
|
2139
2775
|
} }, hostBindings: function SingleListDetailComponent_HostBindings(rf, ctx) { if (rf & 1) {
|
|
2140
2776
|
i0.ɵɵlistener("click", function SingleListDetailComponent_click_HostBindingHandler($event) { return ctx.onDocumentClick($event); }, i0.ɵɵresolveDocument);
|
|
2141
2777
|
} }, inputs: { ListID: "ListID" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 19, vars: 20, consts: [["listDetailGrid", ""], [1, "list-detail-container"], [1, "list-header"], [1, "list-header__titleblock"], [1, "lineage-badge", 3, "title"], [1, "list-detail__tags"], [1, "add-dropdown-wrapper"], [1, "permission-badge", "permission-badge--viewer"], [1, "custom-toolbar"], [1, "loading-container"], [1, "grid-with-stats"], [3, "Confirm", "Cancel", "Visible", "Delta", "TargetListName", "SourceLabel"], [3, "Visible", "Title", "MinWidth", "Width", "Height"], ["Title", "Remove from List", 3, "Visible", "MinWidth", "Width"], ["Title", "Add Records from Views", 3, "Visible", "MinWidth", "Width", "Height"], [1, "fa-solid", "fa-link"], [1, "lineage-badge__refreshed"], ["EntityName", "MJ: Lists", 3, "Provider", "RecordID", "Editable", "MaxDisplay"], ["mjButton", "", "variant", "primary", 1, "add-btn", 3, "click"], [1, "fa-solid", "fa-plus"], [1, "fa-solid", "fa-chevron-down", "add-dropdown-arrow"], [1, "add-dropdown-panel"], [1, "add-dropdown-item"], [1, "add-dropdown-item", 3, "click"], [1, "add-dropdown-item-text"], [1, "add-dropdown-item-desc"], [1, "fa-solid", "fa-eye"], [1, "toolbar-left"], [1, "row-count"], [1, "selection-count"], [1, "toolbar-right"], ["mjButton", "", "variant", "flat", 1, "toolbar-button", 3, "click"], [1, "fa-solid", "fa-rotate"], [1, "fa-solid", "fa-file-export"], ["mjButton", "", "variant", "flat", "title", "Move selected records to another list", 1, "toolbar-button", 3, "click"], [1, "fa-solid", "fa-arrow-right-arrow-left"], ["mjButton", "", "variant", "flat", "title", "Copy selected records to another list (keeps them here too)", 1, "toolbar-button", 3, "click"], [1, "fa-solid", "fa-copy"], ["title", "Set status on selected items", 1, "mj-input", "mj-select", "bulk-status-select", 3, "ngModelChange", "ngModel", "disabled"], ["value", ""], ["value", "Active"], ["value", "Complete"], ["value", "Pending"], ["value", "Disabled"], ["value", "Rejected"], ["mjButton", "", "variant", "flat", 1, "toolbar-button", 3, "click", "disabled"], ["mjButton", "", "variant", "flat", 1, "toolbar-button", "remove-btn", 3, "click"], [1, "fa-solid", "fa-trash"], [1, "fa-solid", "fa-spinner", "fa-spin"], [1, "fa-solid", "fa-pen-to-square"], ["title", "Refresh mode", 1, "mj-input", "mj-select", "refresh-mode-select", 3, "ngModelChange", "ngModel", "disabled"], ["value", "Additive"], ["value", "Sync"], [1, "fa-solid", "fa-cloud-arrow-down"], ["text", "Loading list...", "size", "medium"], [1, "grid-container"], [3, "rowClicked", "rowDoubleClicked", "selectionChange", "dataLoaded", "listId", "listEntity", "autoNavigate", "height", "showToolbar", "toolbarConfig", "selectionMode"], [1, "list-detail__stats"], [3, "Provider", "ListID", "RefreshTrigger"], [3, "Close", "Visible", "Title", "MinWidth", "Width", "Height"], [1, "dialog-content", "move-copy-dialog"], [1, "dialog-loading"], ["mjButton", "", "variant", "primary", 3, "click", "disabled"], ["mjButton", "", "variant", "outline", 3, "click", "disabled"], ["size", "small", 3, "text"], [1, "progress-bar", 3, "Value"], [1, "dialog-instruction"], [1, "search-section"], [1, "search-input-wrapper"], [1, "fa-solid", "fa-search", "search-icon"], ["type", "text", "placeholder", "Filter lists by name\u2026", 1, "mj-input", "search-input", 3, "ngModelChange", "ngModel"], [1, "list-loading"], ["Size", "compact", "Icon", "fa-solid fa-list", "Title", "No other lists found for this entity."], [1, "records-scroll", "move-copy-targets"], ["text", "Loading lists...", "size", "small"], [1, "dialog-note"], [1, "record-item", 3, "selected"], [1, "record-item", 3, "click"], [1, "record-checkbox"], ["type", "radio", "name", "moveCopyTarget", 3, "click", "change", "checked"], [1, "record-name"], [1, "target-desc"], ["Title", "Remove from List", 3, "Close", "Visible", "MinWidth", "Width"], [1, "dialog-content"], [1, "dialog-message"], ["mjButton", "", "variant", "danger", 3, "click", "disabled"], ["text", "Removing records...", "size", "small"], [1, "fa-solid", "fa-triangle-exclamation", "warning-icon"], [1, "dialog-content", "add-records-dialog"], ["text", "Adding records to list...", "size", "small"], ["type", "text", 1, "mj-input", "search-input", 3, "input", "placeholder", "value"], [1, "search-hint"], [1, "records-list"], ["Variant", "no-results", "Size", "compact", "Icon", "fa-solid fa-magnifying-glass", 3, "Title"], ["Size", "compact", "Icon", "fa-solid fa-list", "Title", "Search for records to add to this list"], ["size", "small", 3, "showText"], [1, "selection-controls"], ["mjButton", "", "variant", "flat", "size", "sm", 3, "click"], [1, "selection-info"], [1, "records-scroll"], [1, "record-item", 3, "in-list", "selected"], ["title", "Already in list", 1, "fa-solid", "fa-check", "in-list-icon"], ["type", "checkbox", 3, "checked"], [1, "in-list-badge"], ["type", "checkbox", 3, "click", "change", "checked"], [1, "dialog-content", "export-dialog"], [1, "export-section-label"], [1, "export-format-row"], [1, "export-format-option", 3, "click"], ["type", "radio", "name", "exportFormat", "value", "excel", 3, "ngModelChange", "ngModel"], [1, "fa-solid", "fa-file-excel", 2, "color", "var(--mj-status-success)"], ["type", "radio", "name", "exportFormat", "value", "csv", 3, "ngModelChange", "ngModel"], [1, "fa-solid", "fa-file-csv", 2, "color", "var(--mj-text-secondary)"], ["type", "radio", "name", "exportFormat", "value", "json", 3, "ngModelChange", "ngModel"], [1, "fa-solid", "fa-file-code", 2, "color", "var(--mj-brand-primary)"], [1, "export-section-divider"], [1, "export-fields-list"], [1, "export-field-row"], [1, "export-field-actions"], ["href", "#", 3, "click"], [1, "muted"], ["type", "checkbox", 3, "ngModelChange", "ngModel"], [1, "fa-solid", "fa-download"], ["Title", "Add Records from Views", 3, "Close", "Visible", "MinWidth", "Width", "Height"], ["text", "Loading records from views...", "size", "small"], ["text", "Loading views...", "size", "small"], [1, "views-list"], ["Size", "compact", "Icon", "fa-solid fa-folder-open", "Title", "No saved views found for this entity"], [1, "view-item", 3, "selected"], [1, "view-item", 3, "click"], [1, "fa-solid", "fa-table-list", "view-icon"], [1, "view-name"]], template: function SingleListDetailComponent_Template(rf, ctx) { if (rf & 1) {
|
|
@@ -2196,7 +2832,7 @@ export class SingleListDetailComponent extends BaseAngularComponent {
|
|
|
2196
2832
|
args: [{ standalone: false, selector: 'mj-list-detail', template: "<div class=\"list-detail-container\">\n <!-- Header with title and Add button -->\n <div class=\"list-header\">\n <div class=\"list-header__titleblock\">\n @if (listRecord) {\n <h1>{{ listRecord.Name }}</h1>\n } @else {\n <h1>List</h1>\n }\n @if (hasLineage) {\n <div class=\"lineage-badge\" [title]=\"'Source view: ' + (sourceViewName ?? 'unknown')\">\n <i class=\"fa-solid fa-link\"></i>\n <span>\n @if (listRecord?.UseSnapshot) {\n Snapshot of: <strong>{{ sourceViewName ?? '(unknown view)' }}</strong>\n } @else {\n Live view: <strong>{{ sourceViewName ?? '(unknown view)' }}</strong>\n }\n </span>\n @if (listRecord?.LastRefreshedAt) {\n <span class=\"lineage-badge__refreshed\">\n \u00B7 last refreshed {{ listRecord!.LastRefreshedAt | date:'medium' }}\n </span>\n }\n </div>\n }\n @if (listRecord) {\n <!-- Tags section (Phase 4.2). Editable on the detail view. -->\n <div class=\"list-detail__tags\">\n <mj-tag-chips\n [Provider]=\"Provider\"\n EntityName=\"MJ: Lists\"\n [RecordID]=\"listRecord.ID\"\n [Editable]=\"capabilities.CanEdit\"\n [MaxDisplay]=\"20\">\n </mj-tag-chips>\n </div>\n }\n </div>\n @if (capabilities.CanEdit) {\n <div class=\"add-dropdown-wrapper\">\n <button mjButton variant=\"primary\" class=\"add-btn\" (click)=\"toggleAddDropdown()\">\n <span class=\"fa-solid fa-plus\"></span>\n Add to List\n <span class=\"fa-solid fa-chevron-down add-dropdown-arrow\"></span>\n </button>\n @if (showAddDropdown) {\n <div class=\"add-dropdown-panel\">\n @for (option of addOptions; track option.Text) {\n <div class=\"add-dropdown-item\" (click)=\"onDropdownItemClick(option)\">\n <span class=\"add-dropdown-item-text\">{{ option.Text }}</span>\n @if (option.Description) {\n <span class=\"add-dropdown-item-desc\">{{ option.Description }}</span>\n }\n </div>\n }\n </div>\n }\n </div>\n } @else if (currentLevel) {\n <!-- Viewer-perspective badge so users understand why mutation\n controls are absent. Mirrors mockup 19. -->\n <span class=\"permission-badge permission-badge--viewer\">\n <i class=\"fa-solid fa-eye\"></i>\n Viewer access\n </span>\n }\n </div>\n\n <!-- Custom Toolbar -->\n @if (!showLoader && listRecord) {\n <div class=\"custom-toolbar\">\n <div class=\"toolbar-left\">\n <span class=\"row-count\">{{ rowCount }} row{{ rowCount !== 1 ? 's' : '' }}</span>\n @if (selectedKeys.length > 0) {\n <span class=\"selection-count\">({{ selectedKeys.length }} selected)</span>\n }\n </div>\n <div class=\"toolbar-right\">\n @if (selectedKeys.length > 0 && capabilities.CanEdit) {\n <!-- Bulk-edit toolbar (Phase 5.2, mockups 23/24).\n Move/Copy open the target-list picker. Move then routes\n through the delta-confirm dialog because the source side\n drops; Copy is additive and applies directly. -->\n <button mjButton\n (click)=\"openMoveDialog()\"\n variant=\"flat\"\n class=\"toolbar-button\"\n title=\"Move selected records to another list\">\n <span class=\"fa-solid fa-arrow-right-arrow-left\"></span>\n Move\u2026\n </button>\n <button mjButton\n (click)=\"openCopyDialog()\"\n variant=\"flat\"\n class=\"toolbar-button\"\n title=\"Copy selected records to another list (keeps them here too)\">\n <span class=\"fa-solid fa-copy\"></span>\n Copy\u2026\n </button>\n <select\n class=\"mj-input mj-select bulk-status-select\"\n [(ngModel)]=\"bulkStatus\"\n [disabled]=\"isApplyingBulkStatus\"\n title=\"Set status on selected items\">\n <option value=\"\">Set status\u2026</option>\n <option value=\"Active\">Active</option>\n <option value=\"Complete\">Complete</option>\n <option value=\"Pending\">Pending</option>\n <option value=\"Disabled\">Disabled</option>\n <option value=\"Rejected\">Rejected</option>\n </select>\n <button mjButton\n (click)=\"applyBulkStatus()\"\n [disabled]=\"!bulkStatus || isApplyingBulkStatus\"\n variant=\"flat\"\n class=\"toolbar-button\">\n @if (isApplyingBulkStatus) {\n <span class=\"fa-solid fa-spinner fa-spin\"></span>\n Applying\u2026\n } @else {\n <span class=\"fa-solid fa-pen-to-square\"></span>\n Apply Status\n }\n </button>\n <button mjButton\n (click)=\"openRemoveDialog()\"\n variant=\"flat\"\n class=\"toolbar-button remove-btn\">\n <span class=\"fa-solid fa-trash\"></span>\n Remove from List\n </button>\n }\n @if (hasLineage && capabilities.CanRefresh) {\n <select\n class=\"mj-input mj-select refresh-mode-select\"\n [ngModel]=\"refreshMode\"\n (ngModelChange)=\"onRefreshModeChange($event)\"\n [disabled]=\"isPreviewingRefresh || isApplyingRefresh\"\n title=\"Refresh mode\">\n <option value=\"Additive\">Additive</option>\n <option value=\"Sync\">Sync</option>\n </select>\n <button mjButton\n (click)=\"onRefreshFromSource()\"\n [disabled]=\"isPreviewingRefresh || isApplyingRefresh\"\n variant=\"flat\"\n class=\"toolbar-button\">\n @if (isPreviewingRefresh) {\n <span class=\"fa-solid fa-spinner fa-spin\"></span>\n Previewing...\n } @else {\n <span class=\"fa-solid fa-cloud-arrow-down\"></span>\n Refresh from Source\n }\n </button>\n }\n <button mjButton\n (click)=\"onRefreshClick()\"\n variant=\"flat\"\n class=\"toolbar-button\">\n <span class=\"fa-solid fa-rotate\"></span>\n Refresh\n </button>\n <button mjButton\n (click)=\"onExportClick()\"\n variant=\"flat\"\n class=\"toolbar-button\">\n <span class=\"fa-solid fa-file-export\"></span>\n Export\n </button>\n </div>\n </div>\n }\n\n <!-- Loading State -->\n @if (showLoader) {\n <div class=\"loading-container\">\n <mj-loading text=\"Loading list...\" size=\"medium\"></mj-loading>\n </div>\n }\n\n <!-- Grid + side stats panel (Phase 5.4). -->\n @if (!showLoader && listRecord) {\n <div class=\"grid-with-stats\">\n <div class=\"grid-container\">\n <mj-list-detail-grid\n #listDetailGrid\n [listId]=\"ListID\"\n [listEntity]=\"listRecord\"\n [autoNavigate]=\"true\"\n [height]=\"'auto'\"\n [showToolbar]=\"false\"\n [toolbarConfig]=\"gridToolbarConfig\"\n [selectionMode]=\"'checkbox'\"\n (rowClicked)=\"onRowClicked($event)\"\n (rowDoubleClicked)=\"onRowDoubleClicked($event)\"\n (selectionChange)=\"onSelectionChange($event)\"\n (dataLoaded)=\"onDataLoaded($event)\">\n </mj-list-detail-grid>\n </div>\n <aside class=\"list-detail__stats\">\n <mj-list-stats [Provider]=\"Provider\" [ListID]=\"listRecord.ID\" [RefreshTrigger]=\"statsRefreshTrigger\"></mj-list-stats>\n </aside>\n </div>\n }\n\n <!-- Refresh-from-source delta-confirm dialog -->\n <mj-list-delta-confirm\n [Visible]=\"refreshConfirmVisible && !!refreshDelta\"\n [Delta]=\"refreshDelta\"\n [TargetListName]=\"listRecord?.Name ?? null\"\n [SourceLabel]=\"sourceViewName\"\n (Confirm)=\"onRefreshConfirmCommit($event)\"\n (Cancel)=\"onRefreshConfirmCancel()\">\n </mj-list-delta-confirm>\n\n <!-- Move delta-confirm dialog (mockup 24). Shown only for Move,\n never for Copy. Drops on the source side require the user to\n acknowledge before Confirm enables. -->\n <mj-list-delta-confirm\n [Visible]=\"moveDeltaConfirmVisible && !!moveDelta\"\n [Delta]=\"moveDelta\"\n [TargetListName]=\"listRecord?.Name ?? null\"\n [SourceLabel]=\"'Move to ' + (moveCopySelectedTarget?.Name ?? 'target list')\"\n (Confirm)=\"onMoveConfirmCommit($event)\"\n (Cancel)=\"onMoveConfirmCancel()\">\n </mj-list-delta-confirm>\n</div>\n\n<!-- Move/Copy target picker (mockup 23). Lists every list owned by /\n shared with the current user that targets the same entity. -->\n@if (showMoveCopyDialog) {\n <mj-dialog\n [Visible]=\"true\"\n [Title]=\"(moveCopyMode === 'move' ? 'Move ' : 'Copy ') + selectedKeys.length + ' record' + (selectedKeys.length === 1 ? '' : 's') + ' to\u2026'\"\n (Close)=\"closeMoveCopyDialog()\"\n [MinWidth]=\"480\"\n [Width]=\"640\"\n [Height]=\"560\">\n <div class=\"dialog-content move-copy-dialog\">\n @if (isApplyingMoveCopy) {\n <div class=\"dialog-loading\">\n <mj-loading [text]=\"(moveCopyMode === 'move' ? 'Moving' : 'Copying') + ' records...'\" size=\"small\"></mj-loading>\n @if (moveCopyTotal > 0) {\n <mj-progress-bar\n [Value]=\"moveCopyProgressPercent\"\n class=\"progress-bar\">\n </mj-progress-bar>\n }\n </div>\n } @else {\n <p class=\"dialog-instruction\">\n Choose the target list. Both lists must hold the same entity ({{ listRecord?.Entity ?? 'records' }}).\n </p>\n <div class=\"search-section\">\n <div class=\"search-input-wrapper\">\n <span class=\"fa-solid fa-search search-icon\"></span>\n <input\n class=\"mj-input search-input\"\n type=\"text\"\n placeholder=\"Filter lists by name\u2026\"\n [(ngModel)]=\"moveCopyTargetSearch\" />\n </div>\n </div>\n @if (moveCopyTargetCandidatesLoading) {\n <div class=\"list-loading\">\n <mj-loading text=\"Loading lists...\" size=\"small\"></mj-loading>\n </div>\n } @else if (filteredMoveCopyTargets.length === 0) {\n <mj-empty-state\n Size=\"compact\"\n Icon=\"fa-solid fa-list\"\n Title=\"No other lists found for this entity.\">\n <p class=\"dialog-note\">Create a new list first if you need a new target.</p>\n </mj-empty-state>\n } @else {\n <div class=\"records-scroll move-copy-targets\">\n @for (target of filteredMoveCopyTargets; track target.ID) {\n <div class=\"record-item\"\n [class.selected]=\"moveCopySelectedTarget?.ID === target.ID\"\n (click)=\"selectMoveCopyTarget(target)\">\n <div class=\"record-checkbox\">\n <input type=\"radio\"\n name=\"moveCopyTarget\"\n [checked]=\"moveCopySelectedTarget?.ID === target.ID\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"selectMoveCopyTarget(target)\">\n </div>\n <div class=\"record-name\">\n <strong>{{ target.Name }}</strong>\n @if (target.Description) {\n <span class=\"target-desc\"> \u2014 {{ target.Description }}</span>\n }\n </div>\n </div>\n }\n </div>\n }\n }\n </div>\n <mj-dialog-actions>\n <button mjButton\n (click)=\"confirmMoveCopy()\"\n [disabled]=\"!moveCopySelectedTarget || isApplyingMoveCopy\"\n variant=\"primary\">\n @if (moveCopyMode === 'move') {\n <span class=\"fa-solid fa-arrow-right-arrow-left\"></span>\n Continue\n } @else {\n <span class=\"fa-solid fa-copy\"></span>\n Copy {{ selectedKeys.length }}\n }\n </button>\n <button mjButton\n (click)=\"closeMoveCopyDialog()\"\n [disabled]=\"isApplyingMoveCopy\"\n variant=\"outline\">\n Cancel\n </button>\n </mj-dialog-actions>\n </mj-dialog>\n}\n\n<!-- Remove from List Confirmation Dialog -->\n@if (showRemoveDialog) {\n <mj-dialog\n [Visible]=\"true\"\n Title=\"Remove from List\"\n (Close)=\"closeRemoveDialog()\"\n [MinWidth]=\"350\"\n [Width]=\"450\">\n <div class=\"dialog-content\">\n @if (isRemoving) {\n <div class=\"dialog-loading\">\n <mj-loading text=\"Removing records...\" size=\"small\"></mj-loading>\n @if (removeTotal > 0) {\n <mj-progress-bar\n [Value]=\"removeProgressPercent\"\n class=\"progress-bar\">\n </mj-progress-bar>\n }\n </div>\n } @else {\n <div class=\"dialog-message\">\n <span class=\"fa-solid fa-triangle-exclamation warning-icon\"></span>\n <p>Are you sure you want to remove <strong>{{ selectedKeys.length }}</strong> record{{ selectedKeys.length !== 1 ? 's' : '' }} from this list?</p>\n <p class=\"dialog-note\">This will not delete the records, only remove them from this list.</p>\n </div>\n }\n </div>\n <mj-dialog-actions>\n <button mjButton\n (click)=\"confirmRemoveFromList()\"\n [disabled]=\"isRemoving\"\n variant=\"danger\">\n Remove\n </button>\n <button mjButton\n (click)=\"closeRemoveDialog()\"\n [disabled]=\"isRemoving\"\n variant=\"outline\">\n Cancel\n </button>\n </mj-dialog-actions>\n </mj-dialog>\n}\n\n<!-- Add Records Dialog -->\n@if (showAddRecordsDialog) {\n <mj-dialog\n [Visible]=\"true\"\n [Title]=\"'Add ' + (listRecord?.Entity || 'Records') + ' to List'\"\n (Close)=\"closeAddRecordsDialog()\"\n [MinWidth]=\"500\"\n [Width]=\"700\"\n [Height]=\"650\">\n <div class=\"dialog-content add-records-dialog\">\n @if (addDialogSaving) {\n <div class=\"dialog-loading\">\n <mj-loading text=\"Adding records to list...\" size=\"small\"></mj-loading>\n @if (addTotal > 0) {\n <mj-progress-bar\n [Value]=\"addProgressPercent\"\n class=\"progress-bar\">\n </mj-progress-bar>\n }\n </div>\n } @else {\n <!-- Search Input -->\n <div class=\"search-section\">\n <div class=\"search-input-wrapper\">\n <span class=\"fa-solid fa-search search-icon\"></span>\n <input\n class=\"mj-input search-input\"\n type=\"text\"\n [placeholder]=\"'Search ' + (listRecord?.Entity || 'records') + '...'\"\n [value]=\"addRecordsSearchFilter\"\n (input)=\"onAddRecordsSearchInputEvent($event)\" />\n </div>\n <span class=\"search-hint\">Type at least 2 characters to search</span>\n </div>\n <!-- Records List -->\n <div class=\"records-list\">\n @if (addDialogLoading) {\n <div class=\"list-loading\">\n <mj-loading [showText]=\"false\" size=\"small\"></mj-loading>\n </div>\n } @else if (addableRecords.length === 0 && addRecordsSearchFilter.length >= 2) {\n <mj-empty-state\n Variant=\"no-results\"\n Size=\"compact\"\n Icon=\"fa-solid fa-magnifying-glass\"\n [Title]=\"AddRecordsNoMatchTitle\" />\n } @else if (addableRecords.length === 0) {\n <mj-empty-state\n Size=\"compact\"\n Icon=\"fa-solid fa-list\"\n Title=\"Search for records to add to this list\" />\n } @else {\n <!-- Selection controls -->\n <div class=\"selection-controls\">\n <button mjButton variant=\"flat\" size=\"sm\" (click)=\"selectAllAddable()\">Select All</button>\n <button mjButton variant=\"flat\" size=\"sm\" (click)=\"deselectAllAddable()\">Deselect All</button>\n <span class=\"selection-info\">{{ selectedAddableRecords.length }} selected</span>\n </div>\n <!-- Records -->\n <div class=\"records-scroll\">\n @for (record of addableRecords; track record.ID) {\n <div class=\"record-item\"\n [class.in-list]=\"record.isInList\"\n [class.selected]=\"record.isSelected\"\n (click)=\"toggleRecordSelection(record)\">\n <div class=\"record-checkbox\">\n @if (record.isInList) {\n <span class=\"fa-solid fa-check in-list-icon\" title=\"Already in list\"></span>\n } @else {\n <input type=\"checkbox\"\n [checked]=\"record.isSelected\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleRecordSelection(record)\">\n }\n </div>\n <div class=\"record-name\">{{ record.Name }}</div>\n @if (record.isInList) {\n <span class=\"in-list-badge\">In List</span>\n }\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n <mj-dialog-actions>\n <button mjButton\n (click)=\"confirmAddRecords()\"\n [disabled]=\"addDialogSaving || selectedAddableRecords.length === 0\"\n variant=\"primary\">\n Add {{ selectedAddableRecords.length > 0 ? selectedAddableRecords.length : '' }} Record{{ selectedAddableRecords.length !== 1 ? 's' : '' }}\n </button>\n <button mjButton\n (click)=\"closeAddRecordsDialog()\"\n [disabled]=\"addDialogSaving\"\n variant=\"outline\">\n Cancel\n </button>\n </mj-dialog-actions>\n </mj-dialog>\n}\n\n<!-- Export Dialog (mockup 26) \u2014 format + column picker -->\n@if (showExportDialog && listRecord) {\n <mj-dialog\n [Visible]=\"true\"\n [Title]=\"'Export ' + exportRecordCount + ' Record' + (exportRecordCount === 1 ? '' : 's')\"\n (Close)=\"closeExportDialog()\"\n [MinWidth]=\"520\"\n [Width]=\"640\"\n [Height]=\"640\">\n <div class=\"dialog-content export-dialog\">\n <label class=\"export-section-label\">Format</label>\n <div class=\"export-format-row\">\n <label class=\"export-format-option\"\n [class.export-format-option--selected]=\"exportFormat === 'excel'\"\n (click)=\"exportFormat = 'excel'\">\n <input type=\"radio\" name=\"exportFormat\" value=\"excel\" [(ngModel)]=\"exportFormat\">\n <i class=\"fa-solid fa-file-excel\" style=\"color: var(--mj-status-success);\"></i>\n <span>Excel (.xlsx)</span>\n </label>\n <label class=\"export-format-option\"\n [class.export-format-option--selected]=\"exportFormat === 'csv'\"\n (click)=\"exportFormat = 'csv'\">\n <input type=\"radio\" name=\"exportFormat\" value=\"csv\" [(ngModel)]=\"exportFormat\">\n <i class=\"fa-solid fa-file-csv\" style=\"color: var(--mj-text-secondary);\"></i>\n <span>CSV</span>\n </label>\n <label class=\"export-format-option\"\n [class.export-format-option--selected]=\"exportFormat === 'json'\"\n (click)=\"exportFormat = 'json'\">\n <input type=\"radio\" name=\"exportFormat\" value=\"json\" [(ngModel)]=\"exportFormat\">\n <i class=\"fa-solid fa-file-code\" style=\"color: var(--mj-brand-primary);\"></i>\n <span>JSON</span>\n </label>\n </div>\n\n <div class=\"export-section-divider\"></div>\n\n <label class=\"export-section-label\">Columns to include</label>\n <div class=\"export-fields-list\">\n @for (f of exportFields; track f.Name) {\n <label class=\"export-field-row\">\n <input type=\"checkbox\" [(ngModel)]=\"f.Selected\">\n <span>{{ f.DisplayName }}</span>\n </label>\n }\n </div>\n <div class=\"export-field-actions\">\n <a href=\"#\" (click)=\"selectAllExportFields(); $event.preventDefault()\">Select All</a>\n <a href=\"#\" (click)=\"selectNoneExportFields(); $event.preventDefault()\">Select None</a>\n <span class=\"muted\">{{ selectedExportFieldCount }} of {{ exportFields.length }} selected</span>\n </div>\n </div>\n <mj-dialog-actions>\n <button mjButton variant=\"primary\"\n [disabled]=\"selectedExportFieldCount === 0 || isExporting\"\n (click)=\"executeExport()\">\n @if (isExporting) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n Exporting\u2026\n } @else {\n <i class=\"fa-solid fa-download\"></i>\n Export {{ exportRecordCount }} Record{{ exportRecordCount === 1 ? '' : 's' }}\n }\n </button>\n <button mjButton variant=\"outline\" [disabled]=\"isExporting\" (click)=\"closeExportDialog()\">Cancel</button>\n </mj-dialog-actions>\n </mj-dialog>\n}\n\n<!-- Add From View Dialog -->\n@if (showAddFromViewDialog) {\n <mj-dialog\n [Visible]=\"true\"\n Title=\"Add Records from Views\"\n (Close)=\"closeAddFromViewDialog()\"\n [MinWidth]=\"400\"\n [Width]=\"600\"\n [Height]=\"500\">\n <div class=\"dialog-content\">\n @if (showAddFromViewLoader && fetchingRecordsToSave) {\n <div class=\"dialog-loading\">\n <mj-loading text=\"Loading records from views...\" size=\"small\"></mj-loading>\n </div>\n } @else if (showAddFromViewLoader && addFromViewTotal > 0) {\n <div class=\"dialog-loading\">\n <mj-loading text=\"Adding records to list...\" size=\"small\"></mj-loading>\n <mj-progress-bar\n [Value]=\"addFromViewProgressPercent\"\n class=\"progress-bar\">\n </mj-progress-bar>\n </div>\n } @else if (showAddFromViewLoader) {\n <div class=\"dialog-loading\">\n <mj-loading text=\"Loading views...\" size=\"small\"></mj-loading>\n </div>\n } @else {\n <p class=\"dialog-instruction\">Select views to add their records to this list:</p>\n <div class=\"views-list\">\n @if (!userViews || userViews.length === 0) {\n <mj-empty-state\n Size=\"compact\"\n Icon=\"fa-solid fa-folder-open\"\n Title=\"No saved views found for this entity\" />\n } @else {\n @for (view of userViews; track view.ID) {\n <div class=\"view-item\"\n [class.selected]=\"isViewSelected(view)\"\n (click)=\"toggleViewSelection(view)\">\n <input type=\"checkbox\"\n [checked]=\"isViewSelected(view)\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleViewSelection(view)\">\n <span class=\"fa-solid fa-table-list view-icon\"></span>\n <span class=\"view-name\">{{ view.Name }}</span>\n </div>\n }\n }\n </div>\n }\n </div>\n <mj-dialog-actions>\n <button mjButton\n (click)=\"confirmAddFromView()\"\n [disabled]=\"showAddFromViewLoader || userViewsToAdd.length === 0\"\n variant=\"primary\">\n Add from {{ userViewsToAdd.length }} View{{ userViewsToAdd.length !== 1 ? 's' : '' }}\n </button>\n <button mjButton\n (click)=\"closeAddFromViewDialog()\"\n [disabled]=\"showAddFromViewLoader\"\n variant=\"outline\">\n Cancel\n </button>\n </mj-dialog-actions>\n </mj-dialog>\n}\n", styles: [".list-detail-container {\n display: flex;\n flex-direction: column;\n height: 100%;\n padding: 20px;\n background: var(--mj-bg-surface-card);\n}\n\n.list-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding-bottom: 16px;\n flex-shrink: 0;\n gap: 16px;\n}\n\n.list-header__titleblock {\n display: flex;\n flex-direction: column;\n gap: 4px;\n min-width: 0;\n}\n\n.list-header h1 {\n margin: 0;\n font-size: 24px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.lineage-badge {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 3px 10px;\n border-radius: 999px;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n border: 1px solid color-mix(in srgb, var(--mj-brand-primary) 25%, var(--mj-border-default));\n color: var(--mj-text-secondary);\n font-size: 12px;\n width: fit-content;\n}\n\n.lineage-badge i {\n color: var(--mj-brand-primary);\n}\n\n.lineage-badge__refreshed {\n color: var(--mj-text-muted);\n}\n\n.refresh-mode-select {\n min-width: 100px;\n font-size: 13px;\n}\n\n.bulk-status-select {\n min-width: 130px;\n font-size: 13px;\n}\n\n/* Viewer-perspective badge (mockup 19) \u2014 surfaced in the header when the\n user has read-only access so the missing mutation controls don't feel\n broken. */\n.permission-badge {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 4px 10px;\n border-radius: 999px;\n font-size: 12px;\n font-weight: 600;\n}\n\n.permission-badge--viewer {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n}\n\n/* Tags section on the list detail header (Phase 4.2). */\n.list-detail__tags {\n margin-top: 8px;\n}\n\n/* Grid + side stats two-column layout (Phase 5.4). Stats hides on\n narrow screens so the grid keeps its breathing room. */\n.grid-with-stats {\n display: grid;\n grid-template-columns: 1fr 280px;\n gap: 16px;\n flex: 1;\n min-height: 0;\n}\n\n.list-detail__stats {\n min-width: 0;\n overflow-y: auto;\n}\n\n@media (max-width: 900px) {\n .grid-with-stats {\n grid-template-columns: 1fr;\n }\n .list-detail__stats {\n display: none;\n }\n}\n\n/* Custom Toolbar */\n.custom-toolbar {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 12px 16px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-bottom: none;\n border-radius: 8px 8px 0 0;\n flex-shrink: 0;\n}\n\n.toolbar-left {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.toolbar-right {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.row-count {\n font-size: 14px;\n font-weight: 500;\n color: var(--mj-text-primary);\n}\n\n.selection-count {\n font-size: 14px;\n color: var(--mj-text-secondary);\n}\n\n.toolbar-button {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 13px;\n}\n\n.toolbar-button span {\n font-size: 14px;\n}\n\n.remove-btn {\n color: var(--mj-status-error);\n}\n\n.loading-container {\n display: flex;\n align-items: center;\n justify-content: center;\n flex: 1;\n min-height: 200px;\n}\n\n.grid-container {\n flex: 1;\n min-height: 0;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 0 0 8px 8px;\n overflow: hidden;\n}\n\n/* Add Dropdown Button */\n.add-dropdown-wrapper {\n position: relative;\n}\n\n.add-btn {\n font-size: 16px;\n padding: 8px 25px;\n border-radius: 10px;\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.add-dropdown-arrow {\n font-size: 12px;\n margin-left: 4px;\n}\n\n.add-dropdown-panel {\n position: absolute;\n top: 100%;\n right: 0;\n margin-top: 4px;\n min-width: 240px;\n background: var(--mj-bg-surface-elevated);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);\n z-index: 100;\n overflow: hidden;\n}\n\n.add-dropdown-item {\n display: flex;\n flex-direction: column;\n padding: 10px 16px;\n cursor: pointer;\n transition: background-color 0.15s;\n}\n\n.add-dropdown-item:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.add-dropdown-item-text {\n font-size: 14px;\n font-weight: 500;\n color: var(--mj-text-primary);\n}\n\n.add-dropdown-item-desc {\n font-size: 12px;\n color: var(--mj-text-secondary);\n margin-top: 2px;\n}\n\n/* ==========================================\n Dialog Styles\n ========================================== */\n\n.dialog-content {\n padding: 8px 0;\n}\n\n.dialog-loading {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 40px 20px;\n gap: 20px;\n}\n\n.dialog-message {\n text-align: center;\n padding: 20px;\n}\n\n.warning-icon {\n font-size: 48px;\n color: var(--mj-status-warning);\n margin-bottom: 16px;\n display: block;\n}\n\n.dialog-message p {\n margin: 8px 0;\n font-size: 15px;\n color: var(--mj-text-primary);\n}\n\n.dialog-note {\n color: var(--mj-text-secondary) !important;\n font-size: 13px !important;\n}\n\n.progress-bar {\n width: 100%;\n max-width: 300px;\n}\n\n/* ==========================================\n Add Records Dialog\n ========================================== */\n\n.add-records-dialog {\n display: flex;\n flex-direction: column;\n height: 100%;\n}\n\n.search-section {\n padding-bottom: 12px;\n border-bottom: 1px solid var(--mj-border-default);\n margin-bottom: 12px;\n}\n\n.search-input-wrapper {\n position: relative;\n display: flex;\n align-items: center;\n}\n\n.search-input-wrapper .search-icon {\n position: absolute;\n left: 10px;\n color: var(--mj-text-disabled);\n font-size: 14px;\n pointer-events: none;\n}\n\n.search-input-wrapper .search-input {\n width: 100%;\n padding-left: 32px;\n}\n\n.search-hint {\n display: block;\n font-size: 12px;\n color: var(--mj-text-disabled);\n margin-top: 6px;\n}\n\n.records-list {\n flex: 1;\n overflow: hidden;\n display: flex;\n flex-direction: column;\n}\n\n.list-loading {\n display: flex;\n justify-content: center;\n padding: 40px;\n}\n\n.selection-controls {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 0;\n border-bottom: 1px solid var(--mj-border-default);\n margin-bottom: 8px;\n}\n\n.selection-info {\n margin-left: auto;\n font-size: 13px;\n color: var(--mj-text-secondary);\n}\n\n.records-scroll {\n flex: 1;\n overflow-y: auto;\n max-height: 280px;\n}\n\n.record-item {\n display: flex;\n align-items: center;\n padding: 10px 12px;\n border-radius: 6px;\n cursor: pointer;\n transition: background-color 0.15s;\n gap: 12px;\n}\n\n.record-item:hover:not(.in-list) {\n background: var(--mj-bg-surface-card);\n}\n\n.record-item.selected {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n}\n\n.record-item.in-list {\n background: var(--mj-bg-surface-card);\n cursor: default;\n opacity: 0.7;\n}\n\n.record-checkbox {\n width: 20px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.record-checkbox input[type=\"checkbox\"] {\n width: 16px;\n height: 16px;\n cursor: pointer;\n}\n\n.in-list-icon {\n color: var(--mj-status-success);\n font-size: 14px;\n}\n\n.record-name {\n flex: 1;\n font-size: 14px;\n color: var(--mj-text-primary);\n}\n\n.in-list-badge {\n font-size: 11px;\n padding: 2px 8px;\n background: color-mix(in srgb, var(--mj-status-success) 10%, var(--mj-bg-surface));\n color: var(--mj-status-success);\n border-radius: 10px;\n font-weight: 500;\n}\n\n/* ==========================================\n Add From View Dialog\n ========================================== */\n\n.dialog-instruction {\n margin: 0 0 16px 0;\n font-size: 14px;\n color: var(--mj-text-secondary);\n}\n\n.views-list {\n max-height: 320px;\n overflow-y: auto;\n}\n\n.view-item {\n display: flex;\n align-items: center;\n padding: 12px;\n border-radius: 6px;\n cursor: pointer;\n transition: background-color 0.15s;\n gap: 12px;\n}\n\n.view-item:hover {\n background: var(--mj-bg-surface-card);\n}\n\n.view-item.selected {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n}\n\n.view-item input[type=\"checkbox\"] {\n width: 16px;\n height: 16px;\n cursor: pointer;\n}\n\n.view-icon {\n color: var(--mj-text-secondary);\n font-size: 16px;\n}\n\n.view-name {\n flex: 1;\n font-size: 14px;\n color: var(--mj-text-primary);\n}\n\n/* Export dialog (Phase 5.1, mockup 26) */\n.export-dialog { display: flex; flex-direction: column; gap: 8px; }\n.export-section-label {\n display: block;\n font-size: 12px;\n font-weight: 500;\n color: var(--mj-text-secondary);\n text-transform: uppercase;\n letter-spacing: 0.4px;\n margin-bottom: 8px;\n}\n.export-format-row { display: flex; gap: 8px; }\n.export-format-option {\n flex: 1;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 12px;\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n cursor: pointer;\n font-size: 13px;\n background: var(--mj-bg-surface);\n}\n.export-format-option--selected {\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 6%, transparent);\n}\n.export-format-option input[type=\"radio\"] { margin: 0; }\n.export-section-divider {\n height: 1px;\n background: var(--mj-border-default);\n margin: 16px 0;\n}\n.export-fields-list {\n max-height: 280px;\n overflow-y: auto;\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n}\n.export-field-row {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n border-bottom: 1px solid var(--mj-border-default);\n font-size: 13px;\n cursor: pointer;\n}\n.export-field-row:last-child { border-bottom: none; }\n.export-field-row:hover { background: var(--mj-bg-surface-hover); }\n.export-field-actions {\n display: flex;\n gap: 12px;\n margin-top: 8px;\n align-items: center;\n font-size: 11.5px;\n}\n.export-field-actions a { color: var(--mj-text-link); }\n.export-field-actions .muted {\n margin-left: auto;\n color: var(--mj-text-muted);\n}\n"] }]
|
|
2197
2833
|
}], () => [{ type: i1.SharedService }, { type: i0.ChangeDetectorRef }, { type: i0.ElementRef }, { type: i2.ExportService }, { type: i3.ListSharingService }], { ListID: [{
|
|
2198
2834
|
type: Input
|
|
2199
|
-
}],
|
|
2835
|
+
}], ListDetailGrid: [{
|
|
2200
2836
|
type: ViewChild,
|
|
2201
2837
|
args: ['listDetailGrid']
|
|
2202
2838
|
}], onDocumentClick: [{
|