@conduction/nextcloud-vue 2.78.0 → 2.80.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bundled-packages.json +0 -2
- package/dist/esm/bundled-packages.json +0 -2
- package/dist/esm/components/CnActionButtons/CnActionButtons.vue2.js.map +1 -1
- package/dist/esm/components/CnActionButtons/CnActionButtons.vue3.js +3 -1
- package/dist/esm/components/CnActionButtons/CnActionButtons.vue3.js.map +1 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js +32 -3
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +115 -104
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +12 -2
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js +39 -3
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +158 -0
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +157 -96
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +139 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +140 -92
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +77 -0
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +5 -2
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +62 -4
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +85 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +43 -4
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +11 -0
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js.map +1 -0
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +736 -0
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js +238 -0
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnFactsWidget/index.js +30 -0
- package/dist/esm/components/CnFactsWidget/index.js.map +1 -0
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue.js +10 -0
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue.js.map +1 -0
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js +551 -0
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js +183 -0
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnFieldListWidget/index.js +29 -0
- package/dist/esm/components/CnFieldListWidget/index.js.map +1 -0
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +173 -5
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +181 -99
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowser.js +29 -11
- package/dist/esm/components/CnFilesBrowser/filesBrowser.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/openRegisterSource.js +252 -0
- package/dist/esm/components/CnFilesBrowser/openRegisterSource.js.map +1 -0
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +66 -18
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +4 -4
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnFormPage/CnFormPage.vue2.js +18 -3
- package/dist/esm/components/CnFormPage/CnFormPage.vue2.js.map +1 -1
- package/dist/esm/components/CnFormPage/CnFormPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +330 -74
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +34 -15
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue.js +10 -0
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue.js.map +1 -0
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js +99 -0
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js.map +1 -0
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js +48 -0
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js.map +1 -0
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +100 -3
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +4 -2
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue2.js +29 -1
- package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js +3 -2
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js.map +1 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +10 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js.map +1 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +387 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +169 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnRecordListWidget/index.js +31 -0
- package/dist/esm/components/CnRecordListWidget/index.js.map +1 -0
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue2.js.map +1 -1
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue3.js +1 -1
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue3.js.map +1 -1
- package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue.js +10 -0
- package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue.js.map +1 -0
- package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue2.js +351 -0
- package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue3.js +71 -0
- package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnSectionsWidget/index.js +35 -0
- package/dist/esm/components/CnSectionsWidget/index.js.map +1 -0
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js +81 -2
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +1 -0
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +152 -8
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +84 -23
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +4 -0
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +13 -0
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js +106 -2
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js +105 -53
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js.map +1 -1
- package/dist/esm/composables/useAppManifest.js +8 -3
- package/dist/esm/composables/useAppManifest.js.map +1 -1
- package/dist/esm/composables/useFormDraft.js +140 -9
- package/dist/esm/composables/useFormDraft.js.map +1 -1
- package/dist/esm/index.js +5 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue2.js +28 -20
- package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue3.js +9 -0
- package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/field-inspection.js +21 -0
- package/dist/esm/integrations/builtin/field-inspection.js.map +1 -1
- package/dist/esm/integrations/offline/checklistSubmission.js +141 -0
- package/dist/esm/integrations/offline/checklistSubmission.js.map +1 -0
- package/dist/esm/integrations/offline/fieldCollectionHelpers.js +70 -5
- package/dist/esm/integrations/offline/fieldCollectionHelpers.js.map +1 -1
- package/dist/esm/integrations/offline/index.js +2 -1
- package/dist/esm/integrations/offline/index.js.map +1 -1
- package/dist/esm/integrations/offline/offlineDb.js +6 -1
- package/dist/esm/integrations/offline/offlineDb.js.map +1 -1
- package/dist/esm/integrations/offline/syncReplayService.js +8 -2
- package/dist/esm/integrations/offline/syncReplayService.js.map +1 -1
- package/dist/esm/l10n/en.json.js +72 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +120 -2
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +1314 -74
- package/dist/esm/utils/boardDate.js +193 -0
- package/dist/esm/utils/boardDate.js.map +1 -0
- package/dist/esm/utils/detailActionModel.js +60 -3
- package/dist/esm/utils/detailActionModel.js.map +1 -1
- package/dist/esm/utils/dynamicProperties.js +5 -1
- package/dist/esm/utils/dynamicProperties.js.map +1 -1
- package/dist/esm/utils/fillTemplate.js +114 -0
- package/dist/esm/utils/fillTemplate.js.map +1 -0
- package/dist/esm/utils/libraryWidgetKeys.js +4 -0
- package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
- package/dist/esm/utils/queryPresets.js +96 -0
- package/dist/esm/utils/queryPresets.js.map +1 -0
- package/dist/esm/utils/resolveFilterTokens.js +11 -0
- package/dist/esm/utils/resolveFilterTokens.js.map +1 -1
- package/dist/esm/utils/schema.js +21 -0
- package/dist/esm/utils/schema.js.map +1 -1
- package/dist/esm/utils/sentinelTokens.js +3 -3
- package/dist/esm/utils/sentinelTokens.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +57 -6
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/esm/utils/widgetDispatch.js +7 -1
- package/dist/esm/utils/widgetDispatch.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +14278 -8169
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +1314 -74
- package/l10n/en.json +72 -1
- package/l10n/nl.json +120 -2
- package/package.json +1 -1
- package/scripts/build-validators.js +26 -0
- package/src/components/CnActionButtons/CnActionButtons.vue +2 -0
- package/src/components/CnActionsBar/CnActionsBar.md +4 -0
- package/src/components/CnActionsBar/CnActionsBar.vue +83 -42
- package/src/components/CnAppRoot/CnAppRoot.vue +12 -2
- package/src/components/CnBannerWidget/CnBannerWidget.vue +39 -3
- package/src/components/CnCellRenderer/CnCellRenderer.vue +306 -2
- package/src/components/CnDashboardPage/CnDashboardPage.vue +219 -4
- package/src/components/CnDataTable/CnDataTable.md +2 -0
- package/src/components/CnDataTable/CnDataTable.vue +82 -2
- package/src/components/CnDetailPage/CnDetailPage.md +1 -1
- package/src/components/CnDetailPage/CnDetailPage.vue +63 -3
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +127 -1
- package/src/components/CnFactsWidget/CnFactsWidget.vue +1050 -0
- package/src/components/CnFactsWidget/index.js +29 -0
- package/src/components/CnFieldListWidget/CnFieldListWidget.vue +746 -0
- package/src/components/CnFieldListWidget/index.js +28 -0
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +232 -11
- package/src/components/CnFilesBrowser/filesBrowser.js +43 -2
- package/src/components/CnFilesBrowser/index.js +1 -0
- package/src/components/CnFilesBrowser/openRegisterSource.js +248 -0
- package/src/components/CnFormDialog/CnFormDialog.md +9 -3
- package/src/components/CnFormDialog/CnFormDialog.vue +70 -22
- package/src/components/CnFormPage/CnFormPage.vue +18 -3
- package/src/components/CnIndexPage/CnIndexPage.vue +281 -9
- package/src/components/CnIndexPage/CnIndexPageWidgets.vue +169 -0
- package/src/components/CnObjectSidebar/CnFilesTab.vue +103 -4
- package/src/components/CnPageRenderer/CnPageRenderer.vue +29 -1
- package/src/components/CnQuickFilterBar/CnQuickFilterBar.vue +3 -2
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +603 -0
- package/src/components/CnRecordListWidget/index.js +30 -0
- package/src/components/CnSavedViewsControl/CnSavedViewsControl.vue +1 -1
- package/src/components/CnSectionsWidget/CnSectionsWidget.vue +489 -0
- package/src/components/CnSectionsWidget/index.js +34 -0
- package/src/components/CnStagesWidget/CnStagesWidget.vue +81 -2
- package/src/components/CnStatWidget/CnStatWidget.vue +1 -1
- package/src/components/CnTabsWidget/CnTabsWidget.vue +214 -9
- package/src/components/CnWidgetGrid/registerDashboardWidgets.js +4 -0
- package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +13 -0
- package/src/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue +188 -4
- package/src/components/index.js +5 -1
- package/src/composables/__tests__/useFormDraft.spec.js +69 -0
- package/src/composables/useAppManifest.js +8 -3
- package/src/composables/useFormDraft.js +139 -8
- package/src/css/form-field.css +14 -0
- package/src/css/index-page.css +71 -0
- package/src/css/kpi-card.css +32 -0
- package/src/css/look-board-detail.css +87 -0
- package/src/css/look-board-index.css +143 -14
- package/src/css/look-board.css +114 -2
- package/src/css/table.css +56 -0
- package/src/index.js +8 -0
- package/src/integrations/builtin/field-inspection/CnFieldInspectionCard.vue +35 -20
- package/src/integrations/builtin/field-inspection.js +21 -0
- package/src/integrations/offline/checklistSubmission.js +137 -0
- package/src/integrations/offline/fieldCollectionHelpers.js +69 -4
- package/src/integrations/offline/index.js +3 -0
- package/src/integrations/offline/offlineDb.js +6 -1
- package/src/integrations/offline/syncReplayService.js +8 -2
- package/src/schemas/app-manifest-v2.schema.json +149 -5
- package/src/utils/boardDate.js +189 -0
- package/src/utils/detailActionModel.js +59 -2
- package/src/utils/dynamicProperties.js +5 -1
- package/src/utils/fillTemplate.js +110 -0
- package/src/utils/libraryWidgetKeys.js +4 -0
- package/src/utils/queryPresets.js +93 -0
- package/src/utils/resolveFilterTokens.js +11 -0
- package/src/utils/schema.js +21 -0
- package/src/utils/sentinelTokens.js +3 -3
- package/src/utils/validateManifestV2.compiled.js +57 -2
- package/src/utils/widgetDispatch.js +7 -1
- package/dist/esm/_virtual/equal.js +0 -4
- package/dist/esm/_virtual/equal.js.map +0 -1
- package/dist/esm/_virtual/ucs2length.js +0 -4
- package/dist/esm/_virtual/ucs2length.js.map +0 -1
- package/dist/esm/node_modules/ajv/dist/runtime/equal.js +0 -9
- package/dist/esm/node_modules/ajv/dist/runtime/equal.js.map +0 -1
- package/dist/esm/node_modules/ajv/dist/runtime/ucs2length.js +0 -25
- package/dist/esm/node_modules/ajv/dist/runtime/ucs2length.js.map +0 -1
- package/dist/esm/node_modules/fast-deep-equal/index.js +0 -47
- package/dist/esm/node_modules/fast-deep-equal/index.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="cn-index-page"
|
|
3
|
-
:class="lookClass"
|
|
3
|
+
:class="[lookClass, { 'cn-index-page--with-side-panel': hasSidePanel }]"
|
|
4
4
|
data-testid="cn-index-page"
|
|
5
5
|
@keydown="onListKeydown">
|
|
6
6
|
<!-- Header — overridable via #header slot. CnPageHeader ALWAYS renders:
|
|
@@ -76,6 +76,20 @@
|
|
|
76
76
|
<slot name="below-header" />
|
|
77
77
|
</div>
|
|
78
78
|
|
|
79
|
+
<!-- KPI tiles above the list (`statRow`) and cards beside it
|
|
80
|
+
(`sidePanel`), as DqTeamwachtrij and PqContracten
|
|
81
|
+
(screens-index-stat-row). -->
|
|
82
|
+
<CnIndexPageWidgets
|
|
83
|
+
v-if="statRow && statRow.length > 0"
|
|
84
|
+
class="cn-index-page__stat-row"
|
|
85
|
+
variant="stat-row"
|
|
86
|
+
:widgets="statRow" />
|
|
87
|
+
<CnIndexPageWidgets
|
|
88
|
+
v-if="hasSidePanel"
|
|
89
|
+
class="cn-index-page__side-panel"
|
|
90
|
+
variant="side-panel"
|
|
91
|
+
:widgets="sidePanel" />
|
|
92
|
+
|
|
79
93
|
<!-- Actions bar -->
|
|
80
94
|
<CnActionsBar
|
|
81
95
|
:layout="isBoardLook ? 'board' : 'nextcloud'"
|
|
@@ -97,6 +111,7 @@
|
|
|
97
111
|
:viewMode="currentViewMode"
|
|
98
112
|
:showViewToggle="showViewToggle"
|
|
99
113
|
:availableViewModes="effectiveToggleModes"
|
|
114
|
+
:disabledViewModes="viewSwitchDisabledModes"
|
|
100
115
|
:cardsLabel="cardsLabel"
|
|
101
116
|
:tableLabel="tableLabel"
|
|
102
117
|
:listLabel="listLabel"
|
|
@@ -111,7 +126,7 @@
|
|
|
111
126
|
:sortValue="sortSelectValue"
|
|
112
127
|
:showSearch="inlineSearch || isBoardLook"
|
|
113
128
|
:searchValue="effectiveSearchValue"
|
|
114
|
-
:searchPlaceholder="searchPlaceholder"
|
|
129
|
+
:searchPlaceholder="searchPlaceholder ? cnTranslate(searchPlaceholder) : searchPlaceholder"
|
|
115
130
|
:showCountWithSearch="showCountWithSearch"
|
|
116
131
|
:refreshing="effectiveRefreshing"
|
|
117
132
|
:refreshDisabled="refreshDisabled"
|
|
@@ -565,6 +580,7 @@
|
|
|
565
580
|
:includeColumns="includeColumns"
|
|
566
581
|
:columnOverrides="columnOverrides"
|
|
567
582
|
:rowClass="rowClass"
|
|
583
|
+
:rowTitle="rowTitle"
|
|
568
584
|
:filterable="tableHeaderFilters"
|
|
569
585
|
:activeFilters="effectiveActiveFilters"
|
|
570
586
|
:filterRegister="typeof register === 'string' ? register : ''"
|
|
@@ -958,6 +974,7 @@ import CnQuickEditDialog from '../../dialogs/CnQuickEditDialog.vue'
|
|
|
958
974
|
import CnBuildiqEditButton from '../CnBuildiqEditButton/CnBuildiqEditButton.vue'
|
|
959
975
|
import CnEmptyContent from '../CnEmptyContent/CnEmptyContent.vue'
|
|
960
976
|
import CnFollowToggle from '../CnFollowToggle/CnFollowToggle.vue'
|
|
977
|
+
import CnIndexPageWidgets from './CnIndexPageWidgets.vue'
|
|
961
978
|
import { useContextMenu } from '../../composables/index.js'
|
|
962
979
|
import { useLook } from '../../composables/useLook.js'
|
|
963
980
|
import { copyKindsOf } from '../../composables/useObjectCopy.js'
|
|
@@ -1242,6 +1259,7 @@ export default {
|
|
|
1242
1259
|
name: 'CnIndexPage',
|
|
1243
1260
|
|
|
1244
1261
|
components: {
|
|
1262
|
+
CnIndexPageWidgets,
|
|
1245
1263
|
CnBuildiqEditButton,
|
|
1246
1264
|
CnFollowToggle,
|
|
1247
1265
|
NcLoadingIcon,
|
|
@@ -1533,6 +1551,21 @@ export default {
|
|
|
1533
1551
|
default: () => [],
|
|
1534
1552
|
},
|
|
1535
1553
|
|
|
1554
|
+
/**
|
|
1555
|
+
* How the board look draws the table's title column (manifest
|
|
1556
|
+
* `config.rowTitle`): `link` (the default) underlines the title,
|
|
1557
|
+
* `plain` draws it as bold text without an underline over a 13px muted
|
|
1558
|
+
* secondary line. The row stays clickable. The Nextcloud look ignores
|
|
1559
|
+
* it. Maps to CnDataTable `rowTitle`.
|
|
1560
|
+
*
|
|
1561
|
+
* @spec openspec/changes/screens-table-rows-parity/specs/index-list-board-look/spec.md#requirement-a-row-title-can-be-plain-text
|
|
1562
|
+
*/
|
|
1563
|
+
rowTitle: {
|
|
1564
|
+
type: String,
|
|
1565
|
+
default: 'link',
|
|
1566
|
+
validator: (value) => ['link', 'plain'].includes(value),
|
|
1567
|
+
},
|
|
1568
|
+
|
|
1536
1569
|
/** Optional MDI icon name. Defaults to schema.icon when a schema is provided. */
|
|
1537
1570
|
icon: {
|
|
1538
1571
|
type: String,
|
|
@@ -1571,7 +1604,10 @@ export default {
|
|
|
1571
1604
|
* `$route.params` just like the `filter` prop. The first tab with
|
|
1572
1605
|
* `default:true` (else index 0) is active on mount; changing tabs
|
|
1573
1606
|
* re-fetches at page 1. Omit (or `null`) → no tab strip, behaviour
|
|
1574
|
-
* unchanged.
|
|
1607
|
+
* unchanged. Under the board look a tab with `activeLabel` ("Pipeline:
|
|
1608
|
+
* Sales") adds a removable chip with that text to the Active line and
|
|
1609
|
+
* counts on the Filter badge while it is selected; removing the chip
|
|
1610
|
+
* goes back to the default tab.
|
|
1575
1611
|
*/
|
|
1576
1612
|
quickFilters: {
|
|
1577
1613
|
type: Array,
|
|
@@ -1942,6 +1978,22 @@ export default {
|
|
|
1942
1978
|
default: null,
|
|
1943
1979
|
},
|
|
1944
1980
|
|
|
1981
|
+
/**
|
|
1982
|
+
* Board look: the segments the view switch draws, e.g. `['table',
|
|
1983
|
+
* 'cards', 'board', 'map']` as the boards do. A listed mode the page
|
|
1984
|
+
* can open is an ordinary segment; one it cannot (no board columns, no
|
|
1985
|
+
* map configured) is drawn disabled, named "<mode>: not available on
|
|
1986
|
+
* this list". Fed from `pages[].config.viewSwitch`. Ignored without the
|
|
1987
|
+
* board look, and absent the switch is as before.
|
|
1988
|
+
*
|
|
1989
|
+
* @type {Array<'table' | 'cards' | 'board' | 'map'>}
|
|
1990
|
+
* @spec openspec/changes/screens-view-switch-parity/specs/view-switch-board-look/spec.md#requirement-the-manifest-can-draw-segments-the-page-cannot-open
|
|
1991
|
+
*/
|
|
1992
|
+
viewSwitch: {
|
|
1993
|
+
type: Array,
|
|
1994
|
+
default: null,
|
|
1995
|
+
},
|
|
1996
|
+
|
|
1945
1997
|
/**
|
|
1946
1998
|
* The board's configuration, mirroring the manifest `config.board`
|
|
1947
1999
|
* block: `{ statusField, cardFields, swimlaneField }`. The board is
|
|
@@ -2628,6 +2680,35 @@ export default {
|
|
|
2628
2680
|
default: null,
|
|
2629
2681
|
},
|
|
2630
2682
|
|
|
2683
|
+
/**
|
|
2684
|
+
* KPI tiles above the list (manifest `config.statRow`): dashboard
|
|
2685
|
+
* widget definitions `{ id, type, title?, content }`, usually `stat`,
|
|
2686
|
+
* drawn in an auto-fit row (each at least 200px) in the listed order.
|
|
2687
|
+
* Types resolve like dashboard widgets: the app registry, then the
|
|
2688
|
+
* dashboard catalog. Empty by default.
|
|
2689
|
+
*
|
|
2690
|
+
* @spec openspec/changes/screens-index-stat-row/specs/index-page/spec.md#requirement-an-index-page-can-draw-a-stat-row-above-the-list
|
|
2691
|
+
* @type {Array<object>}
|
|
2692
|
+
*/
|
|
2693
|
+
statRow: {
|
|
2694
|
+
type: Array,
|
|
2695
|
+
default: () => [],
|
|
2696
|
+
},
|
|
2697
|
+
|
|
2698
|
+
/**
|
|
2699
|
+
* Cards beside the list (manifest `config.sidePanel`): dashboard
|
|
2700
|
+
* widget definitions `{ id, type, title?, content, headerLink? }`
|
|
2701
|
+
* drawn in a 300px column right of the toolbar and the list. Empty
|
|
2702
|
+
* by default; below 1024px the column moves above the list.
|
|
2703
|
+
*
|
|
2704
|
+
* @spec openspec/changes/screens-index-stat-row/specs/index-page/spec.md#requirement-an-index-page-can-draw-a-side-panel-beside-the-list
|
|
2705
|
+
* @type {Array<object>}
|
|
2706
|
+
*/
|
|
2707
|
+
sidePanel: {
|
|
2708
|
+
type: Array,
|
|
2709
|
+
default: () => [],
|
|
2710
|
+
},
|
|
2711
|
+
|
|
2631
2712
|
/**
|
|
2632
2713
|
* Where a row carries the actions the server says this caller may run
|
|
2633
2714
|
* on it: a dotted path, read off the rows the list already fetched, so
|
|
@@ -3307,6 +3388,16 @@ export default {
|
|
|
3307
3388
|
},
|
|
3308
3389
|
|
|
3309
3390
|
computed: {
|
|
3391
|
+
/**
|
|
3392
|
+
* Whether the page draws cards beside its list (`sidePanel`).
|
|
3393
|
+
*
|
|
3394
|
+
* @spec openspec/changes/screens-index-stat-row/specs/index-page/spec.md#requirement-an-index-page-can-draw-a-side-panel-beside-the-list
|
|
3395
|
+
* @return {boolean}
|
|
3396
|
+
*/
|
|
3397
|
+
hasSidePanel() {
|
|
3398
|
+
return Array.isArray(this.sidePanel) && this.sidePanel.length > 0
|
|
3399
|
+
},
|
|
3400
|
+
|
|
3310
3401
|
/**
|
|
3311
3402
|
* The empty-state copy run through the host translate function
|
|
3312
3403
|
* (the injected `cnTranslate`, identity by default). Only this
|
|
@@ -3679,6 +3770,21 @@ export default {
|
|
|
3679
3770
|
return Object.keys(this.mapConfig || {}).length > 0
|
|
3680
3771
|
},
|
|
3681
3772
|
|
|
3773
|
+
/**
|
|
3774
|
+
* Board look: the modes `viewSwitch` lists that this page cannot open,
|
|
3775
|
+
* drawn as disabled segments.
|
|
3776
|
+
*
|
|
3777
|
+
* @return {Array<string>} The disabled modes.
|
|
3778
|
+
* @spec openspec/changes/screens-view-switch-parity/specs/view-switch-board-look/spec.md#requirement-the-manifest-can-draw-segments-the-page-cannot-open
|
|
3779
|
+
*/
|
|
3780
|
+
viewSwitchDisabledModes() {
|
|
3781
|
+
if (!this.isBoardLook || !Array.isArray(this.viewSwitch)) {
|
|
3782
|
+
return []
|
|
3783
|
+
}
|
|
3784
|
+
const offered = [...this.effectiveToggleModes, ...(this.showMapSegment ? ['map'] : [])]
|
|
3785
|
+
return this.viewSwitch.filter((mode) => ['table', 'cards', 'board', 'map'].includes(mode) && !offered.includes(mode))
|
|
3786
|
+
},
|
|
3787
|
+
|
|
3682
3788
|
/**
|
|
3683
3789
|
* The base view-toggle segments (cards/table/list) passed to CnActionsBar.
|
|
3684
3790
|
* Beta's explicit `viewModes` whitelist wins when set; otherwise the
|
|
@@ -5248,11 +5354,11 @@ export default {
|
|
|
5248
5354
|
return []
|
|
5249
5355
|
}
|
|
5250
5356
|
const props = this.effectiveSchema?.properties || {}
|
|
5251
|
-
const chips = []
|
|
5357
|
+
const chips = [...this.quickFilterActiveChips]
|
|
5252
5358
|
for (const [key, raw] of Object.entries(this.effectiveActiveFilters || {})) {
|
|
5253
5359
|
const values = (Array.isArray(raw) ? raw : [raw])
|
|
5254
5360
|
.filter((value) => value !== null && value !== undefined && value !== '')
|
|
5255
|
-
.map((value) =>
|
|
5361
|
+
.map((value) => this.activeFilterValueLabel(key, value))
|
|
5256
5362
|
if (values.length === 0) {
|
|
5257
5363
|
continue
|
|
5258
5364
|
}
|
|
@@ -5262,6 +5368,87 @@ export default {
|
|
|
5262
5368
|
return chips
|
|
5263
5369
|
},
|
|
5264
5370
|
|
|
5371
|
+
/**
|
|
5372
|
+
* The Active line's chips for the selected quick filters: one per
|
|
5373
|
+
* selected tab that declares an `activeLabel` ("Pipeline: Sales"), run
|
|
5374
|
+
* through the app's translate. A tab without one (an "All" tab) adds no
|
|
5375
|
+
* chip. The chip carries the tab's index so removing it can deselect it.
|
|
5376
|
+
*
|
|
5377
|
+
* @spec openspec/changes/screens-active-filter-line-parity/specs/active-filter-line-board-look/spec.md#requirement-a-quick-filter-with-an-active-label-joins-the-active-line
|
|
5378
|
+
* @return {Array<{key: string, label: string, quickFilterIndex: number}>}
|
|
5379
|
+
*/
|
|
5380
|
+
quickFilterActiveChips() {
|
|
5381
|
+
const tabs = this.tabStripEntries || []
|
|
5382
|
+
if (tabs.length === 0) {
|
|
5383
|
+
return []
|
|
5384
|
+
}
|
|
5385
|
+
const indices = this.quickFilterMultiple
|
|
5386
|
+
? (this.selectedQuickFilterIndices || [])
|
|
5387
|
+
: [this.activeQuickFilterIndex]
|
|
5388
|
+
return indices
|
|
5389
|
+
.filter((index) => typeof index === 'number' && tabs[index] && typeof tabs[index].activeLabel === 'string' && tabs[index].activeLabel !== '')
|
|
5390
|
+
.map((index) => ({ key: `quick-filter:${index}`, label: this.cnTranslate(tabs[index].activeLabel), quickFilterIndex: index }))
|
|
5391
|
+
},
|
|
5392
|
+
|
|
5393
|
+
/**
|
|
5394
|
+
* Filter keys over a reference property that the Active line must name,
|
|
5395
|
+
* and that no reference column resolves already: one spec each, with the
|
|
5396
|
+
* referenced register and schema off the page schema's `$ref`.
|
|
5397
|
+
*
|
|
5398
|
+
* @spec openspec/changes/screens-active-filter-line-parity/specs/active-filter-line-board-look/spec.md#requirement-an-active-chip-names-the-value-not-its-id
|
|
5399
|
+
* @return {Array<{key: string, labelField: string, register: string, schema: string}>}
|
|
5400
|
+
*/
|
|
5401
|
+
activeFilterRefSpecs() {
|
|
5402
|
+
if (!this.isBoardLook || !this.effectiveSchema) {
|
|
5403
|
+
return []
|
|
5404
|
+
}
|
|
5405
|
+
const keys = Object.keys(this.effectiveActiveFilters || {})
|
|
5406
|
+
if (keys.length === 0) {
|
|
5407
|
+
return []
|
|
5408
|
+
}
|
|
5409
|
+
const covered = new Set(this.refLabelSpecs.map((spec) => spec.key))
|
|
5410
|
+
const fields = fieldsFromSchema(this.effectiveSchema, { includeReadOnly: true })
|
|
5411
|
+
const specs = []
|
|
5412
|
+
for (const key of keys) {
|
|
5413
|
+
if (covered.has(key)) {
|
|
5414
|
+
continue
|
|
5415
|
+
}
|
|
5416
|
+
const field = fields.find((f) => f.key === key)
|
|
5417
|
+
const ref = field && field.reference
|
|
5418
|
+
if (!ref || ref.schema === undefined || ref.schema === null) {
|
|
5419
|
+
continue
|
|
5420
|
+
}
|
|
5421
|
+
specs.push({
|
|
5422
|
+
key,
|
|
5423
|
+
labelField: '',
|
|
5424
|
+
register: ref.register || (typeof this.register === 'string' ? this.register : ''),
|
|
5425
|
+
schema: String(ref.schema),
|
|
5426
|
+
})
|
|
5427
|
+
}
|
|
5428
|
+
return specs
|
|
5429
|
+
},
|
|
5430
|
+
|
|
5431
|
+
/**
|
|
5432
|
+
* The ids each active reference filter holds, per filter key, for the
|
|
5433
|
+
* label lookup of the Active line.
|
|
5434
|
+
*
|
|
5435
|
+
* @return {{[key: string]: string[]}} Distinct ids per filter key.
|
|
5436
|
+
*/
|
|
5437
|
+
activeFilterRefIds() {
|
|
5438
|
+
const out = {}
|
|
5439
|
+
const keys = new Set([...this.refLabelSpecs, ...this.activeFilterRefSpecs].map((spec) => spec.key))
|
|
5440
|
+
for (const [key, raw] of Object.entries(this.effectiveActiveFilters || {})) {
|
|
5441
|
+
if (!keys.has(key)) {
|
|
5442
|
+
continue
|
|
5443
|
+
}
|
|
5444
|
+
out[key] = (Array.isArray(raw) ? raw : [raw])
|
|
5445
|
+
.filter((id) => id !== null && id !== undefined && id !== '' && typeof id !== 'object')
|
|
5446
|
+
.map((id) => String(id))
|
|
5447
|
+
.sort()
|
|
5448
|
+
}
|
|
5449
|
+
return out
|
|
5450
|
+
},
|
|
5451
|
+
|
|
5265
5452
|
/**
|
|
5266
5453
|
* The plural the board bulk band names: "With the selected cases".
|
|
5267
5454
|
* The schema's `titlePlural` (or `plural`), else the lower-cased
|
|
@@ -5344,6 +5531,7 @@ export default {
|
|
|
5344
5531
|
* filled in when one is known, else `description`.
|
|
5345
5532
|
*
|
|
5346
5533
|
* @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-an-index-page-title-is-a-manifest-key
|
|
5534
|
+
* @spec openspec/changes/screens-table-footer-and-system-dates-parity/specs/index-list-board-look/spec.md#requirement-the-count-reads-in-the-users-language
|
|
5347
5535
|
* @return {string}
|
|
5348
5536
|
*/
|
|
5349
5537
|
headerDescription() {
|
|
@@ -5351,9 +5539,16 @@ export default {
|
|
|
5351
5539
|
// Board look: the count line is a template over the rows on this page
|
|
5352
5540
|
// and all rows, "{shown} of {total}" unless the page says otherwise.
|
|
5353
5541
|
if (this.isBoardLook && typeof total === 'number' && total >= 0) {
|
|
5354
|
-
const
|
|
5542
|
+
const shown = this.effectiveObjects.length
|
|
5543
|
+
// The default count line is the library's own string, so it
|
|
5544
|
+
// reads "14 van 14" in Dutch; a page's own template goes
|
|
5545
|
+
// through the app's label lookup.
|
|
5546
|
+
if (!this.countText && !this.countSubtitle) {
|
|
5547
|
+
return t('nextcloud-vue', '{shown} of {total}', { shown, total })
|
|
5548
|
+
}
|
|
5549
|
+
const template = this.countText || this.countSubtitle
|
|
5355
5550
|
return this.cnTranslate(template)
|
|
5356
|
-
.replace('{shown}', String(
|
|
5551
|
+
.replace('{shown}', String(shown))
|
|
5357
5552
|
.replace('{total}', String(total))
|
|
5358
5553
|
}
|
|
5359
5554
|
if (this.countSubtitle && typeof total === 'number' && total >= 0) {
|
|
@@ -5534,6 +5729,14 @@ export default {
|
|
|
5534
5729
|
},
|
|
5535
5730
|
},
|
|
5536
5731
|
|
|
5732
|
+
// The Active line names a referenced value, not its id.
|
|
5733
|
+
activeFilterRefIds: {
|
|
5734
|
+
deep: true,
|
|
5735
|
+
handler() {
|
|
5736
|
+
this.loadRefLabels()
|
|
5737
|
+
},
|
|
5738
|
+
},
|
|
5739
|
+
|
|
5537
5740
|
countRequestKey: {
|
|
5538
5741
|
immediate: true,
|
|
5539
5742
|
/** @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-counts-on-filters-and-views */
|
|
@@ -5802,7 +6005,7 @@ export default {
|
|
|
5802
6005
|
* @spec openspec/changes/index-ref-column-labels/tasks.md#task-2
|
|
5803
6006
|
*/
|
|
5804
6007
|
async loadRefLabels() {
|
|
5805
|
-
const specs = this.refLabelSpecs
|
|
6008
|
+
const specs = [...this.refLabelSpecs, ...this.activeFilterRefSpecs]
|
|
5806
6009
|
if (specs.length === 0) {
|
|
5807
6010
|
return
|
|
5808
6011
|
}
|
|
@@ -5817,7 +6020,8 @@ export default {
|
|
|
5817
6020
|
}
|
|
5818
6021
|
await Promise.all(specs.map(async (spec) => {
|
|
5819
6022
|
const known = this.refLabels[spec.key] || {}
|
|
5820
|
-
const
|
|
6023
|
+
const wanted = new Set([...(this.refLabelIds[spec.key] || []), ...(this.activeFilterRefIds[spec.key] || [])])
|
|
6024
|
+
const ids = [...wanted].filter((id) => !Object.hasOwn(known, id))
|
|
5821
6025
|
if (ids.length === 0) {
|
|
5822
6026
|
return
|
|
5823
6027
|
}
|
|
@@ -6501,6 +6705,11 @@ export default {
|
|
|
6501
6705
|
* @return {void}
|
|
6502
6706
|
*/
|
|
6503
6707
|
onClearFilters() {
|
|
6708
|
+
// "Clear all" on the Active line also undoes the quick filters it
|
|
6709
|
+
// listed (a tab without an `activeLabel` is not a filter there).
|
|
6710
|
+
for (const chip of this.quickFilterActiveChips) {
|
|
6711
|
+
this.deselectQuickFilterChip(chip.quickFilterIndex)
|
|
6712
|
+
}
|
|
6504
6713
|
// Clearing the view brings the person's own columns back.
|
|
6505
6714
|
this.appliedViewColumns = null
|
|
6506
6715
|
this.appliedSavedViewId = ''
|
|
@@ -6591,11 +6800,74 @@ export default {
|
|
|
6591
6800
|
* @spec openspec/changes/screens-index-list-parity/specs/index-list-board-look/spec.md#requirement-the-toolbar-sits-on-the-ground-in-two-rows
|
|
6592
6801
|
*/
|
|
6593
6802
|
onRemoveActiveFilter(chip) {
|
|
6803
|
+
if (chip && typeof chip.quickFilterIndex === 'number') {
|
|
6804
|
+
this.deselectQuickFilterChip(chip.quickFilterIndex)
|
|
6805
|
+
return
|
|
6806
|
+
}
|
|
6594
6807
|
if (chip && typeof chip.key === 'string') {
|
|
6595
6808
|
this.onFilterEvent({ key: chip.key, values: [] })
|
|
6596
6809
|
}
|
|
6597
6810
|
},
|
|
6598
6811
|
|
|
6812
|
+
/**
|
|
6813
|
+
* Undo a quick filter from the Active line. With several tabs selectable
|
|
6814
|
+
* the tab leaves the selection; with one, the page goes back to its
|
|
6815
|
+
* default tab when that tab names no active filter itself, and else to no
|
|
6816
|
+
* tab at all, so the chip does not come straight back.
|
|
6817
|
+
*
|
|
6818
|
+
* @param {number} index The tab index the chip stands for.
|
|
6819
|
+
* @return {void}
|
|
6820
|
+
* @spec openspec/changes/screens-active-filter-line-parity/specs/active-filter-line-board-look/spec.md#requirement-a-quick-filter-with-an-active-label-joins-the-active-line
|
|
6821
|
+
*/
|
|
6822
|
+
deselectQuickFilterChip(index) {
|
|
6823
|
+
if (this.quickFilterMultiple) {
|
|
6824
|
+
this.onQuickFilterMultiChange((this.selectedQuickFilterIndices || []).filter((i) => i !== index))
|
|
6825
|
+
return
|
|
6826
|
+
}
|
|
6827
|
+
if (this.activeQuickFilterIndex !== index) {
|
|
6828
|
+
return
|
|
6829
|
+
}
|
|
6830
|
+
const tabs = this.tabStripEntries || []
|
|
6831
|
+
let fallback = tabs.findIndex((tab) => tab && tab.default === true)
|
|
6832
|
+
if (fallback < 0 && tabs.length > 0) {
|
|
6833
|
+
fallback = 0
|
|
6834
|
+
}
|
|
6835
|
+
const fallbackNamed = fallback >= 0 && typeof tabs[fallback].activeLabel === 'string' && tabs[fallback].activeLabel !== ''
|
|
6836
|
+
this.onQuickFilterChange(fallback >= 0 && fallback !== index && !fallbackNamed ? fallback : null)
|
|
6837
|
+
},
|
|
6838
|
+
|
|
6839
|
+
/**
|
|
6840
|
+
* The text an Active chip shows for one filter value: the referenced
|
|
6841
|
+
* object's label, the facet bucket's label, the schema's `oneOf` title,
|
|
6842
|
+
* else the value itself.
|
|
6843
|
+
*
|
|
6844
|
+
* @param {string} key The filter key.
|
|
6845
|
+
* @param {unknown} value One value of that filter.
|
|
6846
|
+
* @return {string} The label.
|
|
6847
|
+
* @spec openspec/changes/screens-active-filter-line-parity/specs/active-filter-line-board-look/spec.md#requirement-an-active-chip-names-the-value-not-its-id
|
|
6848
|
+
*/
|
|
6849
|
+
activeFilterValueLabel(key, value) {
|
|
6850
|
+
const id = String(value)
|
|
6851
|
+
const refs = this.refLabels[key]
|
|
6852
|
+
if (refs && typeof refs[id] === 'string' && refs[id] !== '') {
|
|
6853
|
+
return refs[id]
|
|
6854
|
+
}
|
|
6855
|
+
const facet = this.effectiveFacetData && this.effectiveFacetData[key]
|
|
6856
|
+
const bucket = facet && Array.isArray(facet.values)
|
|
6857
|
+
? facet.values.find((fv) => fv && typeof fv === 'object' && String(fv.value) === id)
|
|
6858
|
+
: null
|
|
6859
|
+
if (bucket && typeof bucket.label === 'string' && bucket.label !== '') {
|
|
6860
|
+
return this.cnTranslate(bucket.label)
|
|
6861
|
+
}
|
|
6862
|
+
const prop = this.effectiveSchema?.properties?.[key]
|
|
6863
|
+
const options = prop && Array.isArray(prop.oneOf) ? prop.oneOf : []
|
|
6864
|
+
const option = options.find((o) => o && String(o.const) === id && typeof o.title === 'string')
|
|
6865
|
+
if (option) {
|
|
6866
|
+
return this.cnTranslate(option.title)
|
|
6867
|
+
}
|
|
6868
|
+
return id
|
|
6869
|
+
},
|
|
6870
|
+
|
|
6599
6871
|
/**
|
|
6600
6872
|
* A header filter was applied or cleared. One column can own several
|
|
6601
6873
|
* query parameters (a range is `key[gte]` and `key[lte]`), so they are
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
|
|
5
|
+
CnIndexPageWidgets — the widgets an index page draws around its list
|
|
6
|
+
(screens-index-stat-row): the KPI tiles above it (`statRow`, an auto-fit row)
|
|
7
|
+
and the cards beside it (`sidePanel`, a column). Each entry is a dashboard
|
|
8
|
+
widget definition `{ id, type, title?, content, headerLink? }`, resolved like
|
|
9
|
+
a dashboard widget: the app's registry, then the dashboard catalog, then the
|
|
10
|
+
built-in widgets. Internal to CnIndexPage.
|
|
11
|
+
-->
|
|
12
|
+
<template>
|
|
13
|
+
<component
|
|
14
|
+
:is="variant === 'stat-row' ? 'CnKpiGrid' : 'aside'"
|
|
15
|
+
v-if="entries.length > 0"
|
|
16
|
+
:columns="variant === 'stat-row' ? 'auto' : undefined"
|
|
17
|
+
class="cn-index-page-widgets"
|
|
18
|
+
:class="['cn-index-page-widgets--' + variant]"
|
|
19
|
+
:data-testid="'cn-index-page-' + variant">
|
|
20
|
+
<div
|
|
21
|
+
v-for="entry in entries"
|
|
22
|
+
:key="entry.id"
|
|
23
|
+
class="cn-index-page-widgets__cell"
|
|
24
|
+
:data-testid="'cn-index-page-widget-' + entry.id">
|
|
25
|
+
<CnWidgetWrapper
|
|
26
|
+
:widgetId="entry.id"
|
|
27
|
+
:title="entry.title"
|
|
28
|
+
:showTitle="variant === 'side-panel' && entry.title !== ''"
|
|
29
|
+
:showActions="false"
|
|
30
|
+
:showRefresh="false"
|
|
31
|
+
:headerLink="entry.headerLink"
|
|
32
|
+
:flush="variant === 'stat-row'">
|
|
33
|
+
<component
|
|
34
|
+
:is="entry.renderer"
|
|
35
|
+
:widgetId="entry.id"
|
|
36
|
+
:content="entry.content"
|
|
37
|
+
v-bind="entry.content" />
|
|
38
|
+
</CnWidgetWrapper>
|
|
39
|
+
</div>
|
|
40
|
+
</component>
|
|
41
|
+
</template>
|
|
42
|
+
|
|
43
|
+
<script>
|
|
44
|
+
import CnKpiGrid from '../CnKpiGrid/CnKpiGrid.vue'
|
|
45
|
+
import CnWidgetWrapper from '../CnWidgetWrapper/CnWidgetWrapper.vue'
|
|
46
|
+
import { canonicalWidgetType } from '../../utils/widgetTypeAliases.js'
|
|
47
|
+
import { BUILT_IN_WIDGETS } from '../CnWidgetGrid/builtInWidgets.js'
|
|
48
|
+
import { getWidgetTypeEntry } from '../CnWidgetGrid/dashboardWidgetRegistry.js'
|
|
49
|
+
|
|
50
|
+
// Populate the dashboard catalog (stat, table, people, …): an index page
|
|
51
|
+
// may be the first surface that resolves a widget type.
|
|
52
|
+
import '../CnWidgetGrid/registerDashboardWidgets.js'
|
|
53
|
+
|
|
54
|
+
export default {
|
|
55
|
+
name: 'CnIndexPageWidgets',
|
|
56
|
+
|
|
57
|
+
components: {
|
|
58
|
+
CnKpiGrid,
|
|
59
|
+
CnWidgetWrapper,
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
inject: {
|
|
63
|
+
/** Consumer widget registry provided by CnAppRoot (custom over built-in). */
|
|
64
|
+
cnRegistry: { default: () => ({}) },
|
|
65
|
+
/** Host translate function provided by CnAppRoot. */
|
|
66
|
+
cnTranslate: { default: () => (key) => key },
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
props: {
|
|
70
|
+
/**
|
|
71
|
+
* Widget definitions `{ id, type, title?, content?, headerLink? }`.
|
|
72
|
+
* An entry without an id or with a type no layer resolves is skipped.
|
|
73
|
+
*/
|
|
74
|
+
widgets: {
|
|
75
|
+
type: Array,
|
|
76
|
+
default: () => [],
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
/** Where the widgets sit: `stat-row` above the list, `side-panel` beside it. */
|
|
80
|
+
variant: {
|
|
81
|
+
type: String,
|
|
82
|
+
default: 'stat-row',
|
|
83
|
+
validator: (v) => ['stat-row', 'side-panel'].includes(v),
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
computed: {
|
|
88
|
+
/**
|
|
89
|
+
* The renderable entries, in the listed order.
|
|
90
|
+
*
|
|
91
|
+
* @spec openspec/changes/screens-index-stat-row/specs/index-page/spec.md#requirement-an-index-page-can-draw-a-stat-row-above-the-list
|
|
92
|
+
* @return {Array<{id: string, title: string, content: object, headerLink: (object|null), renderer: object}>}
|
|
93
|
+
*/
|
|
94
|
+
entries() {
|
|
95
|
+
const list = Array.isArray(this.widgets) ? this.widgets : []
|
|
96
|
+
const out = []
|
|
97
|
+
for (const def of list) {
|
|
98
|
+
if (!def || typeof def.id !== 'string' || def.id === '' || typeof def.type !== 'string') {
|
|
99
|
+
continue
|
|
100
|
+
}
|
|
101
|
+
const renderer = this.resolveRenderer(def.type)
|
|
102
|
+
if (!renderer) {
|
|
103
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
104
|
+
// eslint-disable-next-line no-console
|
|
105
|
+
console.warn(`[CnIndexPage] ${this.variant} widget "${def.id}" has type "${def.type}", which no widget registry resolves; skipped.`)
|
|
106
|
+
}
|
|
107
|
+
continue
|
|
108
|
+
}
|
|
109
|
+
const translate = typeof this.cnTranslate === 'function' ? this.cnTranslate : (key) => key
|
|
110
|
+
out.push({
|
|
111
|
+
id: def.id,
|
|
112
|
+
title: typeof def.title === 'string' && def.title !== '' ? translate(def.title) : '',
|
|
113
|
+
content: (def.content && typeof def.content === 'object') ? def.content : {},
|
|
114
|
+
headerLink: (def.headerLink && typeof def.headerLink === 'object') ? def.headerLink : null,
|
|
115
|
+
renderer,
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
return out
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
methods: {
|
|
123
|
+
/**
|
|
124
|
+
* Resolve a widget type: the consumer registry, then the dashboard
|
|
125
|
+
* catalog (the type as written, then its alias), then the built-ins.
|
|
126
|
+
*
|
|
127
|
+
* @param {string} type The widget type.
|
|
128
|
+
* @return {object|null} The component, or null.
|
|
129
|
+
*/
|
|
130
|
+
resolveRenderer(type) {
|
|
131
|
+
const consumer = (this.cnRegistry || {})[type]
|
|
132
|
+
if (consumer) {
|
|
133
|
+
return consumer.component ?? consumer
|
|
134
|
+
}
|
|
135
|
+
const entry = getWidgetTypeEntry(type) || getWidgetTypeEntry(canonicalWidgetType(type))
|
|
136
|
+
if (entry && entry.renderer) {
|
|
137
|
+
return entry.renderer
|
|
138
|
+
}
|
|
139
|
+
return BUILT_IN_WIDGETS[type] || BUILT_IN_WIDGETS[canonicalWidgetType(type)] || null
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
}
|
|
143
|
+
</script>
|
|
144
|
+
|
|
145
|
+
<style scoped>
|
|
146
|
+
.cn-index-page-widgets--stat-row {
|
|
147
|
+
margin-bottom: var(--cn-board-section-gap, 20px);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.cn-index-page-widgets__cell {
|
|
151
|
+
display: flex;
|
|
152
|
+
min-width: 0;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.cn-index-page-widgets__cell > * {
|
|
156
|
+
flex: 1 1 auto;
|
|
157
|
+
min-width: 0;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/* The side panel column (DqTeamwachtrij: 300px, 16px between cards). Its
|
|
161
|
+
place beside the toolbar and the list is set by CnIndexPage
|
|
162
|
+
(`cn-index-page--with-side-panel`, index-page.css). */
|
|
163
|
+
.cn-index-page-widgets--side-panel {
|
|
164
|
+
display: flex;
|
|
165
|
+
flex-direction: column;
|
|
166
|
+
gap: 16px;
|
|
167
|
+
min-width: 0;
|
|
168
|
+
}
|
|
169
|
+
</style>
|