@antelopejs/dms 0.7.9 → 0.7.10
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 +29 -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 +74 -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/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
|
@@ -26,6 +26,11 @@ interface ListRowProps {
|
|
|
26
26
|
description?: string;
|
|
27
27
|
/** Meta entries, joined by "·" (the `meta` slot overrides them). */
|
|
28
28
|
meta?: string[];
|
|
29
|
+
/**
|
|
30
|
+
* A last line under the meta, dimmed and mono whatever `mono` says: the
|
|
31
|
+
* evidence behind a feed entry (`400 "Cart is empty" × 16`).
|
|
32
|
+
*/
|
|
33
|
+
detail?: string;
|
|
29
34
|
/** Mono meta (times, paths, ids); the title stays sans. */
|
|
30
35
|
mono?: boolean;
|
|
31
36
|
/**
|
|
@@ -81,6 +86,7 @@ const props = withDefaults(defineProps<ListRowProps>(), {
|
|
|
81
86
|
title: undefined,
|
|
82
87
|
description: undefined,
|
|
83
88
|
meta: undefined,
|
|
89
|
+
detail: undefined,
|
|
84
90
|
mono: false,
|
|
85
91
|
size: "md",
|
|
86
92
|
// Undefined, not false: an absent read state is not "read".
|
|
@@ -123,6 +129,7 @@ const theme = tv({
|
|
|
123
129
|
// The "·" closes each entry followed by another (rather than opening it),
|
|
124
130
|
// so a wrapped line never starts with a separator.
|
|
125
131
|
meta: "flex flex-wrap items-center gap-x-2 gap-y-1 [&>span:has(+span)]:after:text-dimmed [&>span:has(+span)]:after:ms-2 [&>span:has(+span)]:after:content-['·']",
|
|
132
|
+
detail: "text-dimmed mt-0.5 font-mono text-[11px] font-medium",
|
|
126
133
|
trailing: "flex shrink-0 items-center gap-2",
|
|
127
134
|
trailingText:
|
|
128
135
|
"text-dimmed text-right font-mono text-[11.5px] font-medium whitespace-nowrap",
|
|
@@ -183,6 +190,7 @@ const theme = tv({
|
|
|
183
190
|
true: {
|
|
184
191
|
title: "truncate max-sm:whitespace-normal",
|
|
185
192
|
meta: "flex-nowrap overflow-hidden whitespace-nowrap max-sm:flex-wrap max-sm:whitespace-normal",
|
|
193
|
+
detail: "truncate max-sm:whitespace-normal",
|
|
186
194
|
},
|
|
187
195
|
},
|
|
188
196
|
bare: {
|
|
@@ -285,6 +293,13 @@ const unreadText = computed(
|
|
|
285
293
|
</span>
|
|
286
294
|
</slot>
|
|
287
295
|
</div>
|
|
296
|
+
<p
|
|
297
|
+
v-if="props.detail"
|
|
298
|
+
:class="ui.detail()"
|
|
299
|
+
:title="props.truncate ? props.detail : undefined"
|
|
300
|
+
>
|
|
301
|
+
{{ props.detail }}
|
|
302
|
+
</p>
|
|
288
303
|
</div>
|
|
289
304
|
</div>
|
|
290
305
|
<div v-if="hasTrailing && !props.bare" :class="ui.trailing()">
|
|
@@ -10,6 +10,8 @@ import DmsBlockActions, {
|
|
|
10
10
|
type BlockAction,
|
|
11
11
|
} from "../../build/components/blocks/BlockActions.vue";
|
|
12
12
|
import { useChartFetch } from "../../composables/chart/useChartFetch";
|
|
13
|
+
import { readFetchNotFound } from "../../build/utils/fetchNotFound";
|
|
14
|
+
import DmsBlockInlineStatus from "../../build/components/blocks/BlockInlineStatus.vue";
|
|
13
15
|
import { useWatch } from "../../../../dms-core/app/composables/watch/useWatch";
|
|
14
16
|
import { useComposedText } from "../../../../dms-core/app/composables/translation/useComposedText";
|
|
15
17
|
import type { DefaultComponentProps } from "../../../../dms-core/app/types/component";
|
|
@@ -119,6 +121,10 @@ const showSkeleton = computed(() => isLoading.value && !data.value);
|
|
|
119
121
|
const hasError = computed(
|
|
120
122
|
() => !isLoading.value && !data.value && Boolean(error.value),
|
|
121
123
|
);
|
|
124
|
+
// A 404 is a status too, but nothing to retry: the figures do not exist.
|
|
125
|
+
const notFound = computed(() =>
|
|
126
|
+
hasError.value ? readFetchNotFound(error.value) : null,
|
|
127
|
+
);
|
|
122
128
|
// The placeholder is the meter's own shape: the label line (the label is
|
|
123
129
|
// configuration, shown as is), the bar at its size, the legend line.
|
|
124
130
|
const SKELETON_TRACK_HEIGHTS: Record<string, string> = {
|
|
@@ -162,24 +168,15 @@ const Wrapper = props.card ? resolveComponent("DmsCard") : "div";
|
|
|
162
168
|
<USkeleton :aria-label="t('dms.a11y.loading')" class="h-2.5 w-40" />
|
|
163
169
|
</span>
|
|
164
170
|
</div>
|
|
165
|
-
<div v-else-if="hasError" class="grid min-w-0 gap-1.5"
|
|
171
|
+
<div v-else-if="hasError" class="grid min-w-0 gap-1.5">
|
|
166
172
|
<span v-if="props.label" class="text-toned text-[13px]">
|
|
167
173
|
{{ translate(props.label) }}
|
|
168
174
|
</span>
|
|
169
|
-
<
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
<UButton
|
|
175
|
-
:label="t('dms.table.load_error_retry')"
|
|
176
|
-
icon="i-ph-arrows-clockwise"
|
|
177
|
-
color="neutral"
|
|
178
|
-
variant="outline"
|
|
179
|
-
size="xs"
|
|
180
|
-
@click="refresh()"
|
|
181
|
-
/>
|
|
182
|
-
</div>
|
|
175
|
+
<DmsBlockInlineStatus
|
|
176
|
+
:state="notFound ? 'not-found' : 'error'"
|
|
177
|
+
:not-found="notFound"
|
|
178
|
+
@retry="refresh()"
|
|
179
|
+
/>
|
|
183
180
|
</div>
|
|
184
181
|
<DmsMeter
|
|
185
182
|
v-else
|
|
@@ -11,18 +11,28 @@ import type {
|
|
|
11
11
|
PeriodRange,
|
|
12
12
|
} from "#dms-core/app/composables/period/types";
|
|
13
13
|
import type { DefaultComponentProps } from "../../../../dms-core/app/types/component";
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
startingRange,
|
|
16
|
+
usePeriodDraft,
|
|
17
|
+
} from "../../build/composables/period/usePeriodDraft";
|
|
18
|
+
import {
|
|
19
|
+
type ComparisonControl,
|
|
20
|
+
resolveComparisonControl,
|
|
21
|
+
} from "../../build/composables/period/periodComparisonControl";
|
|
22
|
+
import { isRelativePreset } from "#dms-core/app/composables/period/presetResolvers";
|
|
15
23
|
import { usePeriodLocation } from "../../build/composables/period/usePeriodLocation";
|
|
16
24
|
import {
|
|
17
25
|
type CalendarRange,
|
|
18
26
|
calendarToRange,
|
|
19
27
|
formatPeriodRange,
|
|
28
|
+
formatRelativeInstant,
|
|
20
29
|
periodDuration,
|
|
21
30
|
rangeToCalendar,
|
|
22
31
|
} from "../../build/composables/period/periodDisplay";
|
|
23
32
|
import PeriodRangeCalendar from "../../build/components/period/PeriodRangeCalendar.vue";
|
|
24
33
|
import { useUserRegionalPreferences } from "#dms-core/app/composables/user/useUserRegionalPreferences";
|
|
25
34
|
import { usePageLive } from "../../composables/live/usePageLive";
|
|
35
|
+
import { useLivePeriodSpan } from "../../build/composables/live/useLivePeriodSpan";
|
|
26
36
|
|
|
27
37
|
interface Props extends DefaultComponentProps {
|
|
28
38
|
id: string;
|
|
@@ -35,6 +45,9 @@ interface Props extends DefaultComponentProps {
|
|
|
35
45
|
align?: "left" | "center" | "right";
|
|
36
46
|
size?: "xs" | "sm" | "md" | "lg" | "xl";
|
|
37
47
|
showRangeLabel?: boolean;
|
|
48
|
+
rangeLabel?: "absolute" | "relative";
|
|
49
|
+
comparisonControl?: ComparisonControl;
|
|
50
|
+
rememberKey?: string;
|
|
38
51
|
variant?: "default" | "segmented";
|
|
39
52
|
}
|
|
40
53
|
|
|
@@ -44,6 +57,7 @@ const CARET_UP_ICON = "i-ph-caret-up-light";
|
|
|
44
57
|
const CUSTOM_PRESET: PeriodPreset = "custom";
|
|
45
58
|
const NO_COMPARISON: PeriodComparison = "none";
|
|
46
59
|
const SEGMENTED_VARIANT: NonNullable<Props["variant"]> = "segmented";
|
|
60
|
+
const RELATIVE_RANGE_LABEL: NonNullable<Props["rangeLabel"]> = "relative";
|
|
47
61
|
// Two months side by side from this width; one below (design @container 760px).
|
|
48
62
|
const TWO_MONTHS_QUERY = "(min-width: 768px)";
|
|
49
63
|
|
|
@@ -51,6 +65,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
51
65
|
align: "right",
|
|
52
66
|
size: "sm",
|
|
53
67
|
showRangeLabel: true,
|
|
68
|
+
rangeLabel: "absolute",
|
|
54
69
|
variant: "default",
|
|
55
70
|
});
|
|
56
71
|
|
|
@@ -98,19 +113,23 @@ const { t, locale } = useI18n();
|
|
|
98
113
|
// A paused live page keeps its period where it was: a relative preset
|
|
99
114
|
// sliding every minute would refetch every block reading it.
|
|
100
115
|
const { isPaused: isPagePaused } = usePageLive();
|
|
116
|
+
const comparisonSetup = resolveComparisonControl(props);
|
|
101
117
|
const period = usePeriod({
|
|
102
118
|
defaultPreset: props.defaultPreset,
|
|
103
|
-
defaultComparison:
|
|
119
|
+
defaultComparison: comparisonSetup.defaultComparison,
|
|
104
120
|
presets: props.presets,
|
|
105
|
-
comparisons:
|
|
121
|
+
comparisons: comparisonSetup.comparisons,
|
|
106
122
|
holdClock: () => isPagePaused.value,
|
|
107
123
|
});
|
|
124
|
+
// A page declaring `live.slowRateAfter` slows down over a long period.
|
|
125
|
+
useLivePeriodSpan(() => period.state.value.range);
|
|
108
126
|
|
|
109
127
|
// Before the scope registers, so the cards' first request already carries
|
|
110
128
|
// the period of the URL (or of the user's last choice), never the default.
|
|
111
129
|
usePeriodLocation(period, {
|
|
112
130
|
id: props.id,
|
|
113
131
|
pageId: props.pageId,
|
|
132
|
+
rememberKey: props.rememberKey,
|
|
114
133
|
fallback: {
|
|
115
134
|
preset: period.preset.value,
|
|
116
135
|
comparison: period.comparison.value,
|
|
@@ -226,7 +245,7 @@ function applyDraft() {
|
|
|
226
245
|
}
|
|
227
246
|
|
|
228
247
|
const draftCalendar = computed<CalendarRange>(() =>
|
|
229
|
-
rangeToCalendar(draft.range.value),
|
|
248
|
+
rangeToCalendar(draft.range.value ?? startingRange(new Date())),
|
|
230
249
|
);
|
|
231
250
|
|
|
232
251
|
const draftCompareCalendar = computed<CalendarRange | null>(() => {
|
|
@@ -272,7 +291,9 @@ const draftComparison = computed<PeriodComparison>({
|
|
|
272
291
|
});
|
|
273
292
|
|
|
274
293
|
const draftDurationLabel = computed(() => {
|
|
275
|
-
const
|
|
294
|
+
const range = draft.range.value;
|
|
295
|
+
if (!range) return presetLabel(draft.preset.value);
|
|
296
|
+
const { unit, count } = periodDuration(range);
|
|
276
297
|
return t(`dms.period.popover.${unit}`, { count }, count);
|
|
277
298
|
});
|
|
278
299
|
|
|
@@ -282,12 +303,36 @@ const draftCompareLabel = computed(() => {
|
|
|
282
303
|
});
|
|
283
304
|
|
|
284
305
|
// ── Trigger, comparison chip, range label ──────────────────────────────
|
|
285
|
-
const committedRangeLabel = computed(() =>
|
|
286
|
-
|
|
306
|
+
const committedRangeLabel = computed(() => {
|
|
307
|
+
const { range } = period.state.value;
|
|
308
|
+
return range ? formatPeriodRange(range, locale.value) : null;
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* The instant a relative label is read from: a rolling window's own end, so
|
|
313
|
+
* it holds still with the window while the page is paused.
|
|
314
|
+
*/
|
|
315
|
+
function labelNow(preset: PeriodPreset, range: PeriodRange): Date {
|
|
316
|
+
return isRelativePreset(preset) ? range.to : new Date();
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Only a window reaching now reads "since …"; one that has ended keeps its
|
|
320
|
+
// bounds.
|
|
321
|
+
const relativeRangeLabel = computed<string | null>(() => {
|
|
322
|
+
const { preset, range } = period.state.value;
|
|
323
|
+
if (props.rangeLabel !== RELATIVE_RANGE_LABEL || !range) return null;
|
|
324
|
+
const now = labelNow(preset, range);
|
|
325
|
+
if (range.to.getTime() < now.getTime()) return null;
|
|
326
|
+
const from = formatRelativeInstant(range.from, now, locale.value);
|
|
327
|
+
return t("dms.period.since", { from }, `since ${from}`);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
const rangeLabelText = computed(
|
|
331
|
+
() => relativeRangeLabel.value ?? committedRangeLabel.value,
|
|
287
332
|
);
|
|
288
333
|
|
|
289
334
|
const triggerLabel = computed(() =>
|
|
290
|
-
isCustomActive.value
|
|
335
|
+
isCustomActive.value && committedRangeLabel.value
|
|
291
336
|
? committedRangeLabel.value
|
|
292
337
|
: presetLabel(period.preset.value),
|
|
293
338
|
);
|
|
@@ -318,6 +363,21 @@ const canCompare = computed(() =>
|
|
|
318
363
|
period.comparisons.value.some((comparison) => comparison !== NO_COMPARISON),
|
|
319
364
|
);
|
|
320
365
|
|
|
366
|
+
const isComparisonMenu = computed(
|
|
367
|
+
() => canCompare.value && comparisonSetup.control === "menu",
|
|
368
|
+
);
|
|
369
|
+
const toggledComparison = comparisonSetup.toggled;
|
|
370
|
+
const canChooseComparison = computed(
|
|
371
|
+
() => canCompare.value && comparisonSetup.control !== "hidden",
|
|
372
|
+
);
|
|
373
|
+
// Every period has no bounds, so nothing to compare it with.
|
|
374
|
+
const isComparisonDisabled = computed(() => !period.state.value.range);
|
|
375
|
+
|
|
376
|
+
function toggleComparison() {
|
|
377
|
+
if (!toggledComparison) return;
|
|
378
|
+
period.setComparison(hasComparison.value ? NO_COMPARISON : toggledComparison);
|
|
379
|
+
}
|
|
380
|
+
|
|
321
381
|
const comparisonItems = computed(() => [
|
|
322
382
|
period.comparisons.value.map((comparison) => ({
|
|
323
383
|
label: comparisonLabel(comparison),
|
|
@@ -343,7 +403,7 @@ function compareRangeText(compare: PeriodRange, main: PeriodRange): string {
|
|
|
343
403
|
|
|
344
404
|
const compareRangeLabel = computed(() => {
|
|
345
405
|
const { compareRange, range } = period.state.value;
|
|
346
|
-
return compareRange ? compareRangeText(compareRange, range) : null;
|
|
406
|
+
return compareRange && range ? compareRangeText(compareRange, range) : null;
|
|
347
407
|
});
|
|
348
408
|
|
|
349
409
|
// ── Segmented variant ──────────────────────────────────────────────────
|
|
@@ -361,6 +421,13 @@ interface SegmentItem {
|
|
|
361
421
|
icon?: string;
|
|
362
422
|
}
|
|
363
423
|
|
|
424
|
+
function customSegmentLabel(): string {
|
|
425
|
+
const { range } = period.state.value;
|
|
426
|
+
return isCustomActive.value && range
|
|
427
|
+
? formatPeriodRange(range, locale.value, false)
|
|
428
|
+
: presetLabel(CUSTOM_PRESET);
|
|
429
|
+
}
|
|
430
|
+
|
|
364
431
|
const segmentedItems = computed<SegmentItem[]>(() => {
|
|
365
432
|
const items: SegmentItem[] = listedPresets.value.map((preset) => ({
|
|
366
433
|
label: segmentLabel(preset),
|
|
@@ -368,9 +435,7 @@ const segmentedItems = computed<SegmentItem[]>(() => {
|
|
|
368
435
|
}));
|
|
369
436
|
if (isCustomEnabled.value) {
|
|
370
437
|
items.push({
|
|
371
|
-
label:
|
|
372
|
-
? formatPeriodRange(period.state.value.range, locale.value, false)
|
|
373
|
-
: presetLabel(CUSTOM_PRESET),
|
|
438
|
+
label: customSegmentLabel(),
|
|
374
439
|
value: CUSTOM_PRESET,
|
|
375
440
|
icon: CALENDAR_ICON,
|
|
376
441
|
});
|
|
@@ -564,7 +629,7 @@ function onSegmentedClick(event: MouseEvent) {
|
|
|
564
629
|
/>
|
|
565
630
|
</label>
|
|
566
631
|
<label
|
|
567
|
-
v-if="
|
|
632
|
+
v-if="canChooseComparison"
|
|
568
633
|
class="grid gap-1 min-[22rem]:col-span-2 md:col-span-1"
|
|
569
634
|
>
|
|
570
635
|
<span :class="FIELD_LABEL_CLASS">
|
|
@@ -634,15 +699,38 @@ function onSegmentedClick(event: MouseEvent) {
|
|
|
634
699
|
</template>
|
|
635
700
|
</UPopover>
|
|
636
701
|
|
|
702
|
+
<UButton
|
|
703
|
+
v-if="toggledComparison"
|
|
704
|
+
variant="ghost"
|
|
705
|
+
color="neutral"
|
|
706
|
+
:size="size"
|
|
707
|
+
:aria-pressed="hasComparison"
|
|
708
|
+
:disabled="isComparisonDisabled"
|
|
709
|
+
class="order-2"
|
|
710
|
+
:class="hasComparison ? 'text-toned' : 'text-dimmed'"
|
|
711
|
+
@click="toggleComparison"
|
|
712
|
+
>
|
|
713
|
+
<template #leading>
|
|
714
|
+
<span
|
|
715
|
+
class="h-[2.5px] w-3 shrink-0 rounded-[2px]"
|
|
716
|
+
:class="comparisonKeyClass(hasComparison)"
|
|
717
|
+
aria-hidden="true"
|
|
718
|
+
/>
|
|
719
|
+
</template>
|
|
720
|
+
{{ comparisonLabel(toggledComparison) }}
|
|
721
|
+
</UButton>
|
|
722
|
+
|
|
637
723
|
<UDropdownMenu
|
|
638
|
-
v-if="
|
|
724
|
+
v-else-if="isComparisonMenu"
|
|
639
725
|
:items="comparisonItems"
|
|
640
726
|
:content="{ align: 'end' }"
|
|
727
|
+
:disabled="isComparisonDisabled"
|
|
641
728
|
>
|
|
642
729
|
<UButton
|
|
643
730
|
variant="ghost"
|
|
644
731
|
color="neutral"
|
|
645
732
|
:size="size"
|
|
733
|
+
:disabled="isComparisonDisabled"
|
|
646
734
|
:trailing-icon="CARET_DOWN_ICON"
|
|
647
735
|
:aria-label="$t('dms.period.comparison_aria', 'Comparison')"
|
|
648
736
|
class="text-muted order-2"
|
|
@@ -663,12 +751,12 @@ function onSegmentedClick(event: MouseEvent) {
|
|
|
663
751
|
a render that straddles a minute, or a server in another zone, may
|
|
664
752
|
print other bounds than the browser, which then corrects them. -->
|
|
665
753
|
<span
|
|
666
|
-
v-if="showRangeLabel"
|
|
754
|
+
v-if="showRangeLabel && rangeLabelText"
|
|
667
755
|
data-allow-mismatch="text"
|
|
668
756
|
class="text-dimmed hidden font-mono text-[11.5px] font-medium whitespace-nowrap tabular-nums sm:inline"
|
|
669
757
|
:class="rangeLabelOrder"
|
|
670
758
|
>
|
|
671
|
-
<b class="text-toned font-[550]">{{
|
|
759
|
+
<b class="text-toned font-[550]">{{ rangeLabelText }}</b>
|
|
672
760
|
<template v-if="compareRangeLabel">
|
|
673
761
|
·
|
|
674
762
|
<span class="text-(--dms-chart-2)">
|