@antelopejs/dms 0.7.9 → 0.7.11
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/CHANGELOG.md +41 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.js +9 -0
- package/dist/config.js.map +1 -1
- package/dist/implementations/dms/api-base-url.d.ts +1 -0
- package/dist/implementations/dms/api-base-url.js +6 -0
- package/dist/implementations/dms/api-base-url.js.map +1 -0
- package/dist/implementations/dms/frontend-modules.js +7 -6
- package/dist/implementations/dms/frontend-modules.js.map +1 -1
- package/dist/implementations/dms/page.js +8 -2
- package/dist/implementations/dms/page.js.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/test/unit/api-base-url.test.d.ts +1 -0
- package/dist/test/unit/api-base-url.test.js +31 -0
- package/dist/test/unit/api-base-url.test.js.map +1 -0
- package/dist/test/unit/interfaces/dms/page-layout-query.test.d.ts +1 -0
- package/dist/test/unit/interfaces/dms/page-layout-query.test.js +106 -0
- package/dist/test/unit/interfaces/dms/page-layout-query.test.js.map +1 -0
- package/dist/test/unit/interfaces/dms/page-teardown.test.js +1 -0
- package/dist/test/unit/interfaces/dms/page-teardown.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms-base/block-parity-options.test.d.ts +1 -0
- package/dist/test/unit/interfaces/dms-base/block-parity-options.test.js +46 -0
- package/dist/test/unit/interfaces/dms-base/block-parity-options.test.js.map +1 -0
- package/dist/test/unit/interfaces/dms-base/confirm-dialog-secondary-action.test.d.ts +1 -0
- package/dist/test/unit/interfaces/dms-base/confirm-dialog-secondary-action.test.js +26 -0
- package/dist/test/unit/interfaces/dms-base/confirm-dialog-secondary-action.test.js.map +1 -0
- package/dist/test/unit/interfaces/dms-base/data-types/field-types.test.js +10 -0
- package/dist/test/unit/interfaces/dms-base/data-types/field-types.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms-base/guard.test.d.ts +1 -0
- package/dist/test/unit/interfaces/dms-base/guard.test.js +53 -0
- package/dist/test/unit/interfaces/dms-base/guard.test.js.map +1 -0
- package/dist/test/unit/interfaces/dms-base/layout-header-actions.test.js +23 -0
- package/dist/test/unit/interfaces/dms-base/layout-header-actions.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms-base/page-live.test.js +55 -0
- package/dist/test/unit/interfaces/dms-base/page-live.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms-base/period-selector-catalog.test.js +17 -0
- package/dist/test/unit/interfaces/dms-base/period-selector-catalog.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms-base/table-view-reduced-chrome.test.js +2 -0
- package/dist/test/unit/interfaces/dms-base/table-view-reduced-chrome.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms-base/table-view-source-reorder.test.js +28 -0
- package/dist/test/unit/interfaces/dms-base/table-view-source-reorder.test.js.map +1 -1
- package/frontend-vue/layers/dms-core/app/build/composables/user/preferencesCookie.ts +149 -0
- package/frontend-vue/layers/dms-core/app/build/composables/user/tablePreferences.ts +321 -0
- package/frontend-vue/layers/dms-core/app/composables/period/compareResolvers.ts +4 -3
- package/frontend-vue/layers/dms-core/app/composables/period/presetResolvers.ts +4 -3
- package/frontend-vue/layers/dms-core/app/composables/period/types.ts +3 -1
- package/frontend-vue/layers/dms-core/app/composables/period/usePeriod.ts +2 -2
- package/frontend-vue/layers/dms-core/app/composables/period/usePeriodScope.ts +9 -2
- package/frontend-vue/layers/dms-core/app/composables/realtime/usePageRealtime.ts +48 -16
- package/frontend-vue/layers/dms-core/app/composables/realtime/useUserRealtime.ts +21 -4
- package/frontend-vue/layers/dms-core/app/composables/user/usePreferences.ts +81 -49
- package/frontend-vue/layers/dms-core/app/types/confirm-dialog.ts +35 -0
- package/frontend-vue/layers/dms-core/i18n/locales/core-en-GB.json +2 -0
- package/frontend-vue/layers/dms-core/i18n/locales/core-fr-FR.json +2 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardPageBody.vue +10 -1
- package/frontend-vue/layers/dms-layout/app/build/components/layout/PageHeaderActionBar.vue +32 -1
- package/frontend-vue/layers/dms-layout/app/build/components/layout/PageHeaderActionsOutlet.vue +13 -3
- package/frontend-vue/layers/dms-layout/app/build/components/layout/RowSidePanel.vue +27 -3
- package/frontend-vue/layers/dms-layout/app/build/composables/layout/rowSidePanel.ts +16 -0
- package/frontend-vue/layers/dms-layout/app/composables/layout/useServedHeaderActions.ts +75 -0
- package/frontend-vue/layers/dms-layout/app/composables/page/usePageLayout.ts +8 -1
- package/frontend-vue/layers/dms-layout/app/composables/page/useSiteLayout.ts +13 -2
- package/frontend-vue/layers/dms-layout/app/custom-layouts/DefaultLayout.vue +7 -0
- package/frontend-vue/layers/dms-layout/app/custom-layouts/SettingsLayout.vue +7 -0
- package/frontend-vue/layers/dms-layout/app/pages/[...slug].vue +7 -4
- package/frontend-vue/layers/dms-layout/app/utils/pageQuery.ts +59 -0
- package/frontend-vue/layers/dms-layout/i18n/locales/layout-en-GB.json +5 -1
- package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +5 -1
- package/frontend-vue/layers/dms-ui/app/build/components/activity-feed/activityFeedDays.ts +2 -0
- package/frontend-vue/layers/dms-ui/app/build/components/blocks/BlockInlineStatus.vue +67 -0
- package/frontend-vue/layers/dms-ui/app/build/components/blocks/BlockStatus.vue +60 -20
- package/frontend-vue/layers/dms-ui/app/build/components/table/Actions.vue +15 -8
- package/frontend-vue/layers/dms-ui/app/build/components/table/Empty.vue +14 -0
- package/frontend-vue/layers/dms-ui/app/build/components/table/Table.vue +29 -4
- package/frontend-vue/layers/dms-ui/app/build/composables/actions/useActionTargets.ts +76 -28
- package/frontend-vue/layers/dms-ui/app/build/composables/blocks/useBlockItems.ts +26 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/blocks/useGuard.ts +115 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/confirm/actionError.ts +1 -1
- package/frontend-vue/layers/dms-ui/app/build/composables/confirm/confirmDialogTexts.ts +58 -3
- package/frontend-vue/layers/dms-ui/app/build/composables/confirm/secondaryTarget.ts +86 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/confirm/useActionConfirm.ts +5 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/live/currentPageLive.ts +6 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/live/liveCadence.ts +42 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/live/liveHolds.ts +66 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/live/livePoller.ts +42 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/live/liveStorage.ts +42 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/live/missedTally.ts +65 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/live/pageLiveController.ts +247 -88
- package/frontend-vue/layers/dms-ui/app/build/composables/live/useLivePeriodSpan.ts +33 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/live/usePageLiveHost.ts +79 -5
- package/frontend-vue/layers/dms-ui/app/build/composables/period/periodComparisonControl.ts +70 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/period/periodDisplay.ts +55 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/period/periodQuery.ts +41 -5
- package/frontend-vue/layers/dms-ui/app/build/composables/period/usePeriodDraft.ts +22 -9
- package/frontend-vue/layers/dms-ui/app/build/composables/period/usePeriodLocation.ts +13 -6
- package/frontend-vue/layers/dms-ui/app/build/composables/table/useTable.ts +1 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table/useTableColumns.ts +27 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/clearTableFilters.ts +18 -3
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/usePersistedPagination.ts +19 -26
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useScrolledPastTop.ts +66 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableListData.ts +7 -1
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableLiveHold.ts +65 -11
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTablePreferences.ts +149 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableRows.ts +39 -8
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewConfig.ts +1 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewRowActions.ts +16 -11
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViews.ts +39 -16
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/pinnedRows.ts +29 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/recordLink.ts +18 -5
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/sortableColumns.ts +17 -0
- package/frontend-vue/layers/dms-ui/app/build/utils/fetchNotFound.ts +79 -0
- package/frontend-vue/layers/dms-ui/app/build/utils/urlVariables.ts +46 -1
- package/frontend-vue/layers/dms-ui/app/components/activity/ActivityItem.vue +4 -0
- package/frontend-vue/layers/dms-ui/app/components/activity-feed/ActivityFeed.vue +40 -2
- package/frontend-vue/layers/dms-ui/app/components/blocks/CodeBlock.vue +11 -1
- package/frontend-vue/layers/dms-ui/app/components/blocks/KeyValueListBlock.vue +12 -2
- package/frontend-vue/layers/dms-ui/app/components/blocks/LiveToggleBlock.vue +129 -4
- package/frontend-vue/layers/dms-ui/app/components/blocks/NavCardGridBlock.vue +6 -0
- package/frontend-vue/layers/dms-ui/app/components/blocks/StatGroupBlock.vue +6 -1
- package/frontend-vue/layers/dms-ui/app/components/confirm/ConfirmModal.vue +33 -0
- package/frontend-vue/layers/dms-ui/app/components/form/components/ChoiceCards.vue +22 -4
- package/frontend-vue/layers/dms-ui/app/components/guard/Guard.vue +75 -0
- package/frontend-vue/layers/dms-ui/app/components/kpi/KpiCard.vue +12 -17
- package/frontend-vue/layers/dms-ui/app/components/list-row/ListRow.vue +15 -0
- package/frontend-vue/layers/dms-ui/app/components/meter/MeterBlock.vue +12 -15
- package/frontend-vue/layers/dms-ui/app/components/period/PeriodSelector.vue +104 -16
- package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +104 -98
- package/frontend-vue/layers/dms-ui/app/components/top-list/TopListCard.vue +11 -1
- package/frontend-vue/layers/dms-ui/app/composables/chart/types.ts +2 -0
- package/frontend-vue/layers/dms-ui/app/composables/chart/useChartFetch.ts +11 -1
- package/frontend-vue/layers/dms-ui/app/composables/confirm/types.ts +15 -1
- package/frontend-vue/layers/dms-ui/app/composables/live/usePageLive.ts +86 -10
- package/frontend-vue/layers/dms-ui/app/composables/table-view/shortcuts/tableViewShiftF.ts +20 -0
- package/frontend-vue/layers/dms-ui/app/composables/table-view/types/action-target.ts +12 -0
- package/frontend-vue/layers/dms-ui/app/composables/table-view/types/config.ts +5 -0
- package/frontend-vue/layers/dms-ui/app/composables/table-view/useTableViewEmptyState.ts +55 -0
- package/frontend-vue/layers/dms-ui/app/types/live.ts +47 -1
- package/frontend-vue/layers/dms-ui/i18n/locales/ui-en-GB.json +13 -4
- package/frontend-vue/layers/dms-ui/i18n/locales/ui-fr-FR.json +12 -3
- package/frontend-vue/tests/activity-feed-fill-height.test.ts +1 -0
- package/frontend-vue/tests/activity-feed-head.test.ts +140 -0
- package/frontend-vue/tests/block-not-found.test.ts +220 -0
- package/frontend-vue/tests/choice-cards-columns.test.ts +60 -0
- package/frontend-vue/tests/confirm-secondary-action.test.ts +269 -0
- package/frontend-vue/tests/guard.test.ts +242 -0
- package/frontend-vue/tests/header-action-page-query.test.ts +286 -0
- package/frontend-vue/tests/live-toggle.test.ts +87 -1
- package/frontend-vue/tests/page-layout-registration.test.ts +1 -0
- package/frontend-vue/tests/page-live-parity.test.ts +367 -0
- package/frontend-vue/tests/page-realtime-remount.test.ts +185 -0
- package/frontend-vue/tests/period-selector-parity.test.ts +138 -0
- package/frontend-vue/tests/period-selector-url.test.ts +84 -6
- package/frontend-vue/tests/period-url-query.test.ts +52 -0
- package/frontend-vue/tests/preferences-cookie-size.test.ts +311 -0
- package/frontend-vue/tests/row-side-panel.test.ts +109 -10
- package/frontend-vue/tests/tab-slot-spacing.test.ts +8 -0
- package/frontend-vue/tests/table-actions-search-label.test.ts +57 -1
- package/frontend-vue/tests/table-clear-filters.test.ts +99 -1
- package/frontend-vue/tests/table-density-menu.test.ts +4 -3
- package/frontend-vue/tests/table-live-hold.test.ts +95 -5
- package/frontend-vue/tests/table-row-action-labels.test.ts +181 -0
- package/frontend-vue/tests/table-source-period-scope.test.ts +31 -3
- package/frontend-vue/tests/table-view-hidden-filters-page.test.ts +14 -17
- package/frontend-vue/tests/table-view-pinned-rows.test.ts +143 -0
- package/frontend-vue/tests/table-view-sortable-columns.test.ts +45 -0
- package/frontend-vue/tests/table-view-views.test.ts +56 -15
- package/frontend-vue/tests/top-list-card-value-suffix.test.ts +107 -0
- package/frontend-vue/tests/vue-inventory.test.ts +1 -1
- package/package.json +2 -2
- package/skills/dms/SKILL.md +9 -3
- package/skills/dms-module/SKILL.md +5 -0
- package/skills/dms-pages/REFERENCE.md +23 -2
|
@@ -70,6 +70,7 @@ import {
|
|
|
70
70
|
defaultSortingState,
|
|
71
71
|
resolveDefaultSortConfig,
|
|
72
72
|
sanitizeSorting,
|
|
73
|
+
sortingOrDefault,
|
|
73
74
|
sortableColumnIds,
|
|
74
75
|
sortValueKind,
|
|
75
76
|
} from "../../build/composables/table-view/utils/sortableColumns";
|
|
@@ -100,6 +101,7 @@ import {
|
|
|
100
101
|
rowCustomActions,
|
|
101
102
|
} from "../../utils/rowClickAction";
|
|
102
103
|
import { usePermissionPreview } from "#dms-core/app/build/composables/auth/usePermissionPreview";
|
|
104
|
+
import type { RowActionRule } from "#dms-core/app/types/row-action";
|
|
103
105
|
|
|
104
106
|
import UTable from "../../build/components/table/Table.vue";
|
|
105
107
|
import ExpandedRowDetail from "../../build/components/table-view/ExpandedRowDetail.vue";
|
|
@@ -114,7 +116,9 @@ import type {
|
|
|
114
116
|
} from "../../build/components/table/Table.vue";
|
|
115
117
|
import {
|
|
116
118
|
defineAsyncComponent,
|
|
119
|
+
useTemplateRef,
|
|
117
120
|
type Component,
|
|
121
|
+
type ComponentPublicInstance,
|
|
118
122
|
type WatchOptions,
|
|
119
123
|
type WatchSource,
|
|
120
124
|
} from "vue";
|
|
@@ -124,9 +128,13 @@ import { useGroupedRows } from "../../build/composables/table-view/useGroupedRow
|
|
|
124
128
|
import { useTableFooter } from "../../build/composables/table-view/useTableFooter";
|
|
125
129
|
import { useAccumulatedPages } from "../../build/composables/table-view/useAccumulatedPages";
|
|
126
130
|
import { usePersistedPagination } from "../../build/composables/table-view/usePersistedPagination";
|
|
131
|
+
import { useTablePreferences } from "../../build/composables/table-view/useTablePreferences";
|
|
132
|
+
import type { Ref } from "vue";
|
|
133
|
+
import type { TableField } from "#dms-core/app/build/composables/user/tablePreferences";
|
|
127
134
|
import { useTableRows } from "../../build/composables/table-view/useTableRows";
|
|
128
135
|
import { useRefreshTriggers } from "../../build/composables/blocks/useRefreshTriggers";
|
|
129
136
|
import { useTableLiveHold } from "../../build/composables/table-view/useTableLiveHold";
|
|
137
|
+
import type { TableViewLiveHoldOptions } from "../../types/live";
|
|
130
138
|
import { usePageLive } from "../../composables/live/usePageLive";
|
|
131
139
|
import LiveHoldBanner from "../../build/components/table-view/LiveHoldBanner.vue";
|
|
132
140
|
import { useTableReorder } from "../../build/composables/table-view/useTableReorder";
|
|
@@ -169,6 +177,9 @@ interface RealtimePresenceActor {
|
|
|
169
177
|
|
|
170
178
|
type RealtimePresenceMap = Record<string, RealtimePresenceActor[]>;
|
|
171
179
|
|
|
180
|
+
/** A kept field of the table, its state, and its default. */
|
|
181
|
+
type TableStatePreference = [TableField, Ref<unknown>, unknown];
|
|
182
|
+
|
|
172
183
|
// The backend options of a table view, declared here as well so the
|
|
173
184
|
// component's runtime props never depend on resolving the imported config.
|
|
174
185
|
interface TableViewProps<T extends Data> extends TableViewConfig<T> {
|
|
@@ -176,6 +187,8 @@ interface TableViewProps<T extends Data> extends TableViewConfig<T> {
|
|
|
176
187
|
expandable?: TableViewExpandableConfig;
|
|
177
188
|
/** Placeholder of the search field. */
|
|
178
189
|
searchPlaceholder?: string;
|
|
190
|
+
/** Key opening the search (`defineShortcuts` notation); `shift_f` else. */
|
|
191
|
+
searchShortcut?: string;
|
|
179
192
|
/** One-click dropdown filters of the toolbar. */
|
|
180
193
|
quickFilters?: TableViewQuickFilter[];
|
|
181
194
|
/** Rows per page while the user picked none. */
|
|
@@ -186,6 +199,8 @@ interface TableViewProps<T extends Data> extends TableViewConfig<T> {
|
|
|
186
199
|
source?: TableViewSourceConfig;
|
|
187
200
|
/** Rows ordered by hand on a number column (backend `reorder`). */
|
|
188
201
|
reorder?: TableViewReorderConfig;
|
|
202
|
+
/** Rows listed first whatever the sort (backend `pinnedRows`). */
|
|
203
|
+
pinnedRows?: RowActionRule;
|
|
189
204
|
/** Footer texts and figures: row count, hint, summaries and legend. */
|
|
190
205
|
footer?: TableViewFooter;
|
|
191
206
|
/** What the empty body says, per reason it is empty. */
|
|
@@ -199,7 +214,7 @@ interface TableViewProps<T extends Data> extends TableViewConfig<T> {
|
|
|
199
214
|
/** Topics whose events make the table read its rows again. */
|
|
200
215
|
realtimeTopic?: string | string[];
|
|
201
216
|
/** Keeps the rows while the live page is paused (backend `liveHold`). */
|
|
202
|
-
liveHold?: boolean;
|
|
217
|
+
liveHold?: boolean | TableViewLiveHoldOptions;
|
|
203
218
|
}
|
|
204
219
|
|
|
205
220
|
const props = defineProps<TableViewProps<T>>();
|
|
@@ -230,14 +245,10 @@ const {
|
|
|
230
245
|
defaultDisplay,
|
|
231
246
|
} = useTableViewConfig<T>(props);
|
|
232
247
|
|
|
233
|
-
const
|
|
248
|
+
const tablePreferences = useTablePreferences(componentId, pageId);
|
|
234
249
|
const { sendComponentEvent } = useComponentEvent(componentId);
|
|
235
250
|
const { processI18n } = useTranslation();
|
|
236
251
|
|
|
237
|
-
const getTablePreferenceKey = (suffix: string) => {
|
|
238
|
-
return `tables.${componentId}.${pageId}.${suffix}`;
|
|
239
|
-
};
|
|
240
|
-
|
|
241
252
|
const kanbanEligibleColumns = allColumns.filter(isEligibleKanbanColumn);
|
|
242
253
|
const kanbanGroupByOptions = kanbanEligibleColumns.map((column) => ({
|
|
243
254
|
label: processI18n(column.header),
|
|
@@ -260,8 +271,8 @@ const groupedOptions = optionsForDisplay(GROUPED_DISPLAY_ID) as
|
|
|
260
271
|
|
|
261
272
|
// Stored under the legacy "viewMode" key (free migration of "table"/"kanban");
|
|
262
273
|
// clamped to the offered set so a stale/removed id never renders an un-offered display.
|
|
263
|
-
const persistedDisplay =
|
|
264
|
-
|
|
274
|
+
const persistedDisplay = tablePreferences.get<string>(
|
|
275
|
+
"viewMode",
|
|
265
276
|
defaultDisplay,
|
|
266
277
|
);
|
|
267
278
|
// A default display that draws its whole interface (no table chrome, hence no
|
|
@@ -338,30 +349,24 @@ const activeDisplayComponent = computed<Component | string | undefined>(() => {
|
|
|
338
349
|
return STATIC_DISPLAY_COMPONENTS[id];
|
|
339
350
|
});
|
|
340
351
|
|
|
341
|
-
const
|
|
342
|
-
|
|
343
|
-
getTablePreferenceKey("kanbanGroupBy"),
|
|
344
|
-
kanbanOptions?.groupByField ?? "",
|
|
345
|
-
),
|
|
346
|
-
);
|
|
352
|
+
const defaultKanbanGroupBy = kanbanOptions?.groupByField ?? "";
|
|
353
|
+
const kanbanGroupBy = ref<string>(defaultKanbanGroupBy);
|
|
347
354
|
// A saved preference may point to a column that no longer exists or is no
|
|
348
355
|
// longer eligible after a backend config change.
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
)
|
|
353
|
-
|
|
354
|
-
|
|
356
|
+
tablePreferences.restore(
|
|
357
|
+
"kanbanGroupBy",
|
|
358
|
+
kanbanGroupBy,
|
|
359
|
+
(field) =>
|
|
360
|
+
!kanbanOptions ||
|
|
361
|
+
kanbanEligibleColumns.some((c) => c.accessorKey === field),
|
|
362
|
+
);
|
|
355
363
|
|
|
356
364
|
const DEFAULT_DENSITY = "default";
|
|
357
365
|
// The module's density is the default; the user's pick in the ⋯ menu is kept
|
|
358
366
|
// with the rest of the table's state.
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
props.density ?? DEFAULT_DENSITY,
|
|
363
|
-
),
|
|
364
|
-
);
|
|
367
|
+
const defaultDensity = props.density ?? DEFAULT_DENSITY;
|
|
368
|
+
const density = ref<"default" | "compact">(defaultDensity);
|
|
369
|
+
tablePreferences.restore("density", density);
|
|
365
370
|
|
|
366
371
|
const DEFAULT_PAGE_SIZE = 10;
|
|
367
372
|
const DEFAULT_PAGINATION: PaginationState = {
|
|
@@ -406,8 +411,7 @@ const routeParamHiddenFilters = computed<TableFilter[]>(() => {
|
|
|
406
411
|
// A hidden filter's value changed with the URL, in place or through a
|
|
407
412
|
// remount: the page reached among the previous rows lists from page 1.
|
|
408
413
|
const paginationState = usePersistedPagination({
|
|
409
|
-
preferences:
|
|
410
|
-
preferenceKey: getTablePreferenceKey,
|
|
414
|
+
preferences: tablePreferences,
|
|
411
415
|
defaults: DEFAULT_PAGINATION,
|
|
412
416
|
scope: () => [queryParamHiddenFilters.value, routeParamHiddenFilters.value],
|
|
413
417
|
});
|
|
@@ -449,18 +453,14 @@ const groupedGroupByOptions = groupedOptions
|
|
|
449
453
|
value: column.accessorKey,
|
|
450
454
|
}))
|
|
451
455
|
: [];
|
|
452
|
-
const
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
456
|
+
const defaultGroupedGroupBy = groupedOptions?.groupByField ?? "";
|
|
457
|
+
const groupedGroupBy = ref<string>(defaultGroupedGroupBy);
|
|
458
|
+
tablePreferences.restore(
|
|
459
|
+
"groupedGroupBy",
|
|
460
|
+
groupedGroupBy,
|
|
461
|
+
(field) =>
|
|
462
|
+
!groupedOptions || groupedGroupByOptions.some((o) => o.value === field),
|
|
457
463
|
);
|
|
458
|
-
if (
|
|
459
|
-
groupedOptions &&
|
|
460
|
-
!groupedGroupByOptions.some((o) => o.value === groupedGroupBy.value)
|
|
461
|
-
) {
|
|
462
|
-
groupedGroupBy.value = groupedOptions.groupByField;
|
|
463
|
-
}
|
|
464
464
|
const activeGroupedOptions = computed<TableViewGroupedConfig | undefined>(
|
|
465
465
|
() =>
|
|
466
466
|
groupedOptions &&
|
|
@@ -487,19 +487,14 @@ const defaultSortKind = tableDefaultSort
|
|
|
487
487
|
)
|
|
488
488
|
: undefined;
|
|
489
489
|
const sorting = ref<SortingState>(
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
getTablePreferenceKey("sorting"),
|
|
493
|
-
defaultSortState,
|
|
494
|
-
),
|
|
490
|
+
sortingOrDefault(
|
|
491
|
+
tablePreferences.get<SortingState>("sorting", defaultSortState),
|
|
495
492
|
declaredSortableIds,
|
|
493
|
+
defaultSortState,
|
|
496
494
|
),
|
|
497
495
|
);
|
|
498
496
|
|
|
499
|
-
const savedFilters =
|
|
500
|
-
getTablePreferenceKey("columnFilters"),
|
|
501
|
-
[],
|
|
502
|
-
);
|
|
497
|
+
const savedFilters = tablePreferences.get<TableFilter[]>("columnFilters", []);
|
|
503
498
|
|
|
504
499
|
const buildPinnedFilters = (): TableFilter[] =>
|
|
505
500
|
(defaultFilters || []).map((def) => {
|
|
@@ -521,26 +516,17 @@ const initialFilters = [...buildPinnedFilters(), ...manualFilters];
|
|
|
521
516
|
|
|
522
517
|
const columnFilters = ref<TableFilter[]>(initialFilters);
|
|
523
518
|
const columnVisibility = ref<VisibilityState>(
|
|
524
|
-
|
|
525
|
-
getTablePreferenceKey("columnVisibility"),
|
|
526
|
-
initialVisibility,
|
|
527
|
-
),
|
|
528
|
-
);
|
|
529
|
-
const columnSizing = ref<ColumnSizingState>(
|
|
530
|
-
getPreference<ColumnSizingState>(getTablePreferenceKey("columnSizing"), {}),
|
|
531
|
-
);
|
|
532
|
-
const columnOrder = ref<string[]>(
|
|
533
|
-
getPreference<string[]>(getTablePreferenceKey("columnOrder"), []),
|
|
519
|
+
tablePreferences.get<VisibilityState>("columnVisibility", initialVisibility),
|
|
534
520
|
);
|
|
521
|
+
const columnSizing = ref<ColumnSizingState>({});
|
|
522
|
+
tablePreferences.restore("columnSizing", columnSizing);
|
|
523
|
+
const columnOrder = ref<string[]>([]);
|
|
524
|
+
tablePreferences.restore("columnOrder", columnOrder, Array.isArray);
|
|
535
525
|
const hasEmptyDefaultFilter = (defaultFilters || []).some(
|
|
536
526
|
(f) => f.value === undefined || f.value === null || f.value === "",
|
|
537
527
|
);
|
|
538
|
-
const filtersRowOpen = ref<boolean>(
|
|
539
|
-
|
|
540
|
-
getTablePreferenceKey("filtersOpen"),
|
|
541
|
-
hasEmptyDefaultFilter,
|
|
542
|
-
),
|
|
543
|
-
);
|
|
528
|
+
const filtersRowOpen = ref<boolean>(hasEmptyDefaultFilter);
|
|
529
|
+
tablePreferences.restore("filtersOpen", filtersRowOpen);
|
|
544
530
|
const globalFilter = shallowRef<string>();
|
|
545
531
|
const globalFilterDebounced = refDebounced(
|
|
546
532
|
globalFilter,
|
|
@@ -595,7 +581,7 @@ const {
|
|
|
595
581
|
density,
|
|
596
582
|
pagination: paginationState,
|
|
597
583
|
},
|
|
598
|
-
|
|
584
|
+
preferences: tablePreferences,
|
|
599
585
|
urlScope,
|
|
600
586
|
});
|
|
601
587
|
|
|
@@ -607,8 +593,7 @@ const TAB_URL_KEY = "tab";
|
|
|
607
593
|
const urlTab = readTableUrlKey(useDmsRoute().query, urlScope, TAB_URL_KEY);
|
|
608
594
|
// A tab named by the URL wins over the one kept from the last visit.
|
|
609
595
|
const activeTabId = ref<string>(
|
|
610
|
-
urlTab.value ??
|
|
611
|
-
getPreference<string>(getTablePreferenceKey("activeTab"), ALL_TAB_ID),
|
|
596
|
+
urlTab.value ?? tablePreferences.get<string>("activeTab", ALL_TAB_ID),
|
|
612
597
|
);
|
|
613
598
|
|
|
614
599
|
interface ResolvedTab {
|
|
@@ -677,6 +662,8 @@ const tableRows = useTableRows<T>({
|
|
|
677
662
|
routeParams: props.routeParams,
|
|
678
663
|
routeQuery: route.query as Record<string, unknown>,
|
|
679
664
|
}),
|
|
665
|
+
// Groups keep their rows together: the grouped display pins nothing.
|
|
666
|
+
pinnedRows: () => (isGroupedDisplay.value ? undefined : props.pinnedRows),
|
|
680
667
|
});
|
|
681
668
|
// Quick filters: dropdowns of the toolbar over a column's values (a select's
|
|
682
669
|
// items, a boolean, the rows a relation points to), writing that column's
|
|
@@ -822,12 +809,26 @@ const archiveQuery = computed(() =>
|
|
|
822
809
|
archiveMode ? { showArchived: showArchived.value } : {},
|
|
823
810
|
);
|
|
824
811
|
|
|
812
|
+
// The row a side panel has open, which the grid highlights.
|
|
813
|
+
const rowSidePanel = useRowSidePanelContent();
|
|
814
|
+
const rowSidePanelOwner = rowSidePanelOwnerKey(pageId!, componentId!);
|
|
815
|
+
const openRowId = computed(() =>
|
|
816
|
+
rowSidePanel.value?.ownerKey === rowSidePanelOwner
|
|
817
|
+
? rowSidePanel.value.rowKey
|
|
818
|
+
: undefined,
|
|
819
|
+
);
|
|
820
|
+
|
|
825
821
|
// The gate is set up with the other refresh triggers below; the hold only
|
|
826
822
|
// reads it once the table is on screen.
|
|
823
|
+
const tableRoot = useTemplateRef<ComponentPublicInstance>("tableRoot");
|
|
827
824
|
const liveHold = useTableLiveHold({
|
|
828
|
-
|
|
825
|
+
liveHold: props.liveHold,
|
|
829
826
|
isSource: tableRows.isSource,
|
|
830
827
|
gate: () => liveGate,
|
|
828
|
+
signals: {
|
|
829
|
+
isRowOpen: () => openRowId.value !== undefined,
|
|
830
|
+
tableElement: () => tableRoot.value?.$el ?? null,
|
|
831
|
+
},
|
|
831
832
|
});
|
|
832
833
|
|
|
833
834
|
const { data, status, error, refresh } = await useTableListData<T>({
|
|
@@ -881,18 +882,27 @@ watch(listLoadError, (message) => {
|
|
|
881
882
|
});
|
|
882
883
|
|
|
883
884
|
// Whatever sets the sort (a header, the sort menu, a pasted table config), the
|
|
884
|
-
// route only ever receives a sort it accepts
|
|
885
|
+
// route only ever receives a sort it accepts, and a cleared sort goes back to
|
|
886
|
+
// the default one rather than being remembered empty. A new order lists from page 1:
|
|
885
887
|
// the page reached in the previous order shows unrelated rows in the new one.
|
|
886
888
|
// Synchronous, so the list is queried once, with both changes.
|
|
887
889
|
watch(
|
|
888
890
|
sorting,
|
|
889
891
|
(next, previous) => {
|
|
890
|
-
const sanitized =
|
|
892
|
+
const sanitized = sortingOrDefault(
|
|
893
|
+
next,
|
|
894
|
+
sortableIds.value,
|
|
895
|
+
defaultSortState,
|
|
896
|
+
);
|
|
891
897
|
if (!isSameSorting(sanitized, next)) {
|
|
892
898
|
sorting.value = sanitized;
|
|
893
899
|
return;
|
|
894
900
|
}
|
|
895
|
-
const before =
|
|
901
|
+
const before = sortingOrDefault(
|
|
902
|
+
previous,
|
|
903
|
+
sortableIds.value,
|
|
904
|
+
defaultSortState,
|
|
905
|
+
);
|
|
896
906
|
if (
|
|
897
907
|
!isSameSorting(sanitized, before) &&
|
|
898
908
|
paginationState.value.pageIndex !== 0
|
|
@@ -1238,6 +1248,7 @@ const {
|
|
|
1238
1248
|
handleCustomButton,
|
|
1239
1249
|
handleCustomRowAction,
|
|
1240
1250
|
handleBulkCustomAction,
|
|
1251
|
+
handleMissingRow,
|
|
1241
1252
|
runConfirmedBuiltIn,
|
|
1242
1253
|
} = useTableRowActions<T>({
|
|
1243
1254
|
api: $authFetch,
|
|
@@ -1668,25 +1679,17 @@ const openRecordFromUrl = async () => {
|
|
|
1668
1679
|
const id = deepLinkAction ? readRecordId(route.query, urlScope) : undefined;
|
|
1669
1680
|
if (!deepLinkAction || !id) return;
|
|
1670
1681
|
await until(isListLoading).toBe(false);
|
|
1671
|
-
const row = await resolveDeepLinkedRow(id, {
|
|
1682
|
+
const { row, isMissing } = await resolveDeepLinkedRow(id, {
|
|
1672
1683
|
listed: shownResults.value ?? undefined,
|
|
1673
1684
|
idOf: getRowId,
|
|
1674
1685
|
getRow: tableRows.getRow,
|
|
1675
1686
|
});
|
|
1676
1687
|
if (row) handleCustomRowAction(deepLinkAction, row as Data);
|
|
1688
|
+
else if (isMissing) handleMissingRow(deepLinkAction, id);
|
|
1677
1689
|
};
|
|
1678
1690
|
|
|
1679
1691
|
const selectedIds = computed(() => selectedRowIds(rowSelect.value));
|
|
1680
1692
|
|
|
1681
|
-
// The row a side panel has open, which the grid highlights.
|
|
1682
|
-
const rowSidePanel = useRowSidePanelContent();
|
|
1683
|
-
const rowSidePanelOwner = rowSidePanelOwnerKey(pageId!, componentId!);
|
|
1684
|
-
const openRowId = computed(() =>
|
|
1685
|
-
rowSidePanel.value?.ownerKey === rowSidePanelOwner
|
|
1686
|
-
? rowSidePanel.value.rowKey
|
|
1687
|
-
: undefined,
|
|
1688
|
-
);
|
|
1689
|
-
|
|
1690
1693
|
const isRowActionEnabled = (
|
|
1691
1694
|
config: boolean | RowActionConfig | undefined,
|
|
1692
1695
|
item: T,
|
|
@@ -1922,24 +1925,25 @@ defineShortcuts(
|
|
|
1922
1925
|
}),
|
|
1923
1926
|
);
|
|
1924
1927
|
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1928
|
+
// Each kept field with its default: a value back to it is forgotten.
|
|
1929
|
+
const tableStatePreferences: TableStatePreference[] = [
|
|
1930
|
+
["sorting", sorting, defaultSortState],
|
|
1931
|
+
["columnFilters", columnFilters, []],
|
|
1932
|
+
["columnVisibility", columnVisibility, initialVisibility],
|
|
1933
|
+
["filtersOpen", filtersRowOpen, hasEmptyDefaultFilter],
|
|
1934
|
+
["activeTab", activeTabId, ALL_TAB_ID],
|
|
1935
|
+
["viewMode", activeDisplayId, defaultDisplay],
|
|
1936
|
+
["columnOrder", columnOrder, []],
|
|
1937
|
+
["kanbanGroupBy", kanbanGroupBy, defaultKanbanGroupBy],
|
|
1938
|
+
["groupedGroupBy", groupedGroupBy, defaultGroupedGroupBy],
|
|
1939
|
+
["density", density, defaultDensity],
|
|
1940
|
+
];
|
|
1941
|
+
|
|
1942
|
+
tableStatePreferences.forEach(([field, state, defaultValue]) => {
|
|
1939
1943
|
watch(
|
|
1940
|
-
|
|
1944
|
+
state,
|
|
1941
1945
|
(newValue) => {
|
|
1942
|
-
|
|
1946
|
+
tablePreferences.set(field, newValue, defaultValue);
|
|
1943
1947
|
},
|
|
1944
1948
|
{ deep: true },
|
|
1945
1949
|
);
|
|
@@ -1951,7 +1955,7 @@ const COLUMN_SIZING_PERSIST_DEBOUNCE_MS = 500;
|
|
|
1951
1955
|
let pendingColumnSizingWrite = false;
|
|
1952
1956
|
|
|
1953
1957
|
const persistColumnSizing = () => {
|
|
1954
|
-
|
|
1958
|
+
tablePreferences.set("columnSizing", columnSizing.value, {});
|
|
1955
1959
|
pendingColumnSizingWrite = false;
|
|
1956
1960
|
};
|
|
1957
1961
|
|
|
@@ -2030,6 +2034,7 @@ onMounted(() => {
|
|
|
2030
2034
|
|
|
2031
2035
|
<template>
|
|
2032
2036
|
<UTable
|
|
2037
|
+
ref="tableRoot"
|
|
2033
2038
|
v-bind="tableProps"
|
|
2034
2039
|
v-model:pagination="paginationState"
|
|
2035
2040
|
v-model:global-filter="globalFilter"
|
|
@@ -2053,6 +2058,7 @@ onMounted(() => {
|
|
|
2053
2058
|
:reorder="reorderState"
|
|
2054
2059
|
:views-placement="viewsLayout === 'menu' ? 'header' : 'band'"
|
|
2055
2060
|
:search-placeholder="props.searchPlaceholder"
|
|
2061
|
+
:search-shortcut="props.searchShortcut"
|
|
2056
2062
|
:quick-filters="resolvedQuickFilters"
|
|
2057
2063
|
:footer="resolvedFooter"
|
|
2058
2064
|
:empty-states="props.emptyStates"
|
|
@@ -22,6 +22,7 @@ import type {
|
|
|
22
22
|
ValuePrecision,
|
|
23
23
|
} from "../../composables/chart/types";
|
|
24
24
|
import type { DefaultComponentProps } from "../../../../dms-core/app/types/component";
|
|
25
|
+
import { useComposedText } from "../../../../dms-core/app/composables/translation/useComposedText";
|
|
25
26
|
|
|
26
27
|
interface Props extends DefaultComponentProps {
|
|
27
28
|
title: string;
|
|
@@ -32,6 +33,8 @@ interface Props extends DefaultComponentProps {
|
|
|
32
33
|
valueFormat?: ValueFormat;
|
|
33
34
|
currencyCode?: string;
|
|
34
35
|
valuePrecision?: ValuePrecision;
|
|
36
|
+
/** Unit after each formatted value, space included (" ms"); `$` key or literal. */
|
|
37
|
+
valueSuffix?: string;
|
|
35
38
|
showRank?: boolean;
|
|
36
39
|
highlightTopN?: number;
|
|
37
40
|
rankColor?: UiColor;
|
|
@@ -100,6 +103,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
100
103
|
|
|
101
104
|
const { locale, t } = useI18n();
|
|
102
105
|
const { processI18n } = useTranslation();
|
|
106
|
+
const { processText } = useComposedText();
|
|
103
107
|
useComponentEvent(props.componentId);
|
|
104
108
|
const { state: watchState } = useWatch(
|
|
105
109
|
props.watchActions || [],
|
|
@@ -167,14 +171,20 @@ function barClass(index: number): string {
|
|
|
167
171
|
return `${rankFillClass.value} ${QUIET_BAR_OPACITY_CLASS}`;
|
|
168
172
|
}
|
|
169
173
|
|
|
174
|
+
const valueSuffixText = computed(() =>
|
|
175
|
+
props.valueSuffix ? processI18n(props.valueSuffix) : "",
|
|
176
|
+
);
|
|
177
|
+
|
|
170
178
|
function formattedValue(item: TopListItem): string {
|
|
171
|
-
return
|
|
179
|
+
if (item.valueLabel) return processText(item.valueLabel);
|
|
180
|
+
const formatted = formatValue(
|
|
172
181
|
item.value,
|
|
173
182
|
props.valueFormat,
|
|
174
183
|
locale.value,
|
|
175
184
|
props.currencyCode,
|
|
176
185
|
props.valuePrecision,
|
|
177
186
|
);
|
|
187
|
+
return `${formatted}${valueSuffixText.value}`;
|
|
178
188
|
}
|
|
179
189
|
|
|
180
190
|
function sparklineAccentFor(item: TopListItem): string {
|
|
@@ -106,6 +106,8 @@ export interface TopListItem {
|
|
|
106
106
|
title: BlockText;
|
|
107
107
|
description?: BlockText;
|
|
108
108
|
value: number;
|
|
109
|
+
/** The value as written, in place of the formatted one. */
|
|
110
|
+
valueLabel?: BlockText;
|
|
109
111
|
delta?: number | null;
|
|
110
112
|
sparkline?: number[];
|
|
111
113
|
icon?: string;
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
resolveUrlVariables,
|
|
17
17
|
} from "../../build/utils/urlVariables";
|
|
18
18
|
import { useRefreshTriggers } from "../../build/composables/blocks/useRefreshTriggers";
|
|
19
|
+
import { readFetchNotFound } from "../../build/utils/fetchNotFound";
|
|
19
20
|
|
|
20
21
|
export interface UseChartFetchOptions<T> {
|
|
21
22
|
/**
|
|
@@ -41,6 +42,10 @@ export interface UseChartFetchReturn<T> {
|
|
|
41
42
|
* (period, watched state, realtime event) until the new answer lands.
|
|
42
43
|
*/
|
|
43
44
|
isLoading: Ref<boolean>;
|
|
45
|
+
/**
|
|
46
|
+
* Why the last request failed. A 404 also clears `data`: read it with
|
|
47
|
+
* `readFetchNotFound` to draw a not-found state rather than a load error.
|
|
48
|
+
*/
|
|
44
49
|
error: Ref<unknown>;
|
|
45
50
|
refresh: () => Promise<void>;
|
|
46
51
|
}
|
|
@@ -111,7 +116,12 @@ async function runFetch<T>(
|
|
|
111
116
|
data.value = response;
|
|
112
117
|
} catch (err) {
|
|
113
118
|
if (runner.activeAbort !== controller) return;
|
|
114
|
-
if ((err as { name?: string })?.name
|
|
119
|
+
if ((err as { name?: string })?.name === "AbortError") return;
|
|
120
|
+
error.value = err;
|
|
121
|
+
// A 404 says what the block showed is gone (another tab deleted it, the
|
|
122
|
+
// URL now names another record): its last values must not stay on screen
|
|
123
|
+
// as if they still belonged to it. Any other failure keeps them.
|
|
124
|
+
if (readFetchNotFound(err)) data.value = null;
|
|
115
125
|
} finally {
|
|
116
126
|
// A request a newer one superseded leaves the loading flag to that one:
|
|
117
127
|
// clearing it here would show the empty state while the newer answer is
|
|
@@ -24,7 +24,14 @@ export type ConfirmValues = Record<string, unknown>;
|
|
|
24
24
|
* whose texts are already resolved, plus what only a caller in the browser
|
|
25
25
|
* can give (render functions, handlers).
|
|
26
26
|
*/
|
|
27
|
-
export interface ConfirmOptions
|
|
27
|
+
export interface ConfirmOptions
|
|
28
|
+
extends Omit<ConfirmDialog, "params" | "icon" | "secondaryAction"> {
|
|
29
|
+
/**
|
|
30
|
+
* A button next to cancel and confirm that leaves the dialog open (a
|
|
31
|
+
* download before removing): its `run` is awaited, the button loading,
|
|
32
|
+
* and a rejection is shown in the dialog's alert.
|
|
33
|
+
*/
|
|
34
|
+
secondaryAction?: ConfirmSecondaryAction;
|
|
28
35
|
/**
|
|
29
36
|
* The button focused on open (`"cancel"` for a dialog whose safe answer
|
|
30
37
|
* is to stay); left out, the dialog's first focusable element.
|
|
@@ -63,6 +70,13 @@ export interface ConfirmOptions extends Omit<ConfirmDialog, "params" | "icon"> {
|
|
|
63
70
|
) => Promise<void | boolean | ConfirmPartialOutcome>;
|
|
64
71
|
}
|
|
65
72
|
|
|
73
|
+
/** A secondary button of the confirm modal: its text resolved, its handler. */
|
|
74
|
+
export interface ConfirmSecondaryAction {
|
|
75
|
+
label: string;
|
|
76
|
+
icon?: string;
|
|
77
|
+
run: () => Promise<void> | void;
|
|
78
|
+
}
|
|
79
|
+
|
|
66
80
|
/** A summary shown in the confirm modal's alert: a title and its detail. */
|
|
67
81
|
export interface ConfirmNotice {
|
|
68
82
|
title: string;
|