@svadmin/ui 0.73.0 → 0.74.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/README.md +53 -28
- package/dist/app.css +3733 -4119
- package/dist/app.theme.css +3733 -4119
- package/dist/component-registry.d.ts +14977 -0
- package/dist/component-registry.js +216 -0
- package/dist/components/AboutSettings.svelte +7 -1
- package/dist/components/AboutSettings.svelte.d.ts +4 -1
- package/dist/components/ActivityFeed.svelte +9 -3
- package/dist/components/ActivityFeed.svelte.d.ts +3 -0
- package/dist/components/AdminApp.svelte +19 -10
- package/dist/components/AdminApp.svelte.d.ts +6 -0
- package/dist/components/ApiSettings.svelte +41 -5
- package/dist/components/ApprovalCenter.svelte +283 -0
- package/dist/components/ApprovalCenter.svelte.d.ts +10 -0
- package/dist/components/AuditLogViewer.svelte +3 -1
- package/dist/components/AutoForm.svelte +33 -3
- package/dist/components/AutoForm.svelte.d.ts +1 -0
- package/dist/components/AutoTable.svelte +610 -47
- package/dist/components/AutoTable.svelte.d.ts +17 -1
- package/dist/components/BoundRecordDetailDrawer.svelte +9 -0
- package/dist/components/BoundRecordDetailDrawer.svelte.d.ts +2 -0
- package/dist/components/Breadcrumbs.svelte +11 -1
- package/dist/components/CancelTaskButton.svelte +12 -42
- package/dist/components/ColorPicker.svelte +73 -0
- package/dist/components/ColorPicker.svelte.d.ts +15 -0
- package/dist/components/ComboboxField.svelte +114 -27
- package/dist/components/ComboboxField.svelte.d.ts +1 -0
- package/dist/components/DashboardView.svelte +99 -0
- package/dist/components/DashboardView.svelte.d.ts +14 -0
- package/dist/components/DatePicker.svelte +10 -0
- package/dist/components/DatePicker.svelte.d.ts +6 -0
- package/dist/components/DateRangeInput.svelte +85 -5
- package/dist/components/DateRangeInput.svelte.d.ts +10 -0
- package/dist/components/DateRangePicker.svelte +10 -0
- package/dist/components/DateRangePicker.svelte.d.ts +6 -0
- package/dist/components/DateTimeInput.svelte +74 -5
- package/dist/components/DateTimeInput.svelte.d.ts +8 -0
- package/dist/components/DateTimePicker.svelte +10 -0
- package/dist/components/DateTimePicker.svelte.d.ts +6 -0
- package/dist/components/DevTools.svelte +34 -26
- package/dist/components/DevframePanel.svelte +123 -0
- package/dist/components/DevframePanel.svelte.d.ts +3 -0
- package/dist/components/ErrorSummary.svelte +52 -0
- package/dist/components/ErrorSummary.svelte.d.ts +14 -0
- package/dist/components/FieldRenderer.svelte +82 -81
- package/dist/components/FileUpload.svelte +214 -52
- package/dist/components/FileUpload.svelte.d.ts +11 -0
- package/dist/components/FilterBuilder.svelte +88 -66
- package/dist/components/FilterCollectionInput.svelte +80 -0
- package/dist/components/FilterCollectionInput.svelte.d.ts +10 -0
- package/dist/components/GanttChart.svelte +66 -14
- package/dist/components/GanttChart.svelte.d.ts +6 -0
- package/dist/components/Header.svelte +18 -2
- package/dist/components/ImportWizard.svelte +339 -17
- package/dist/components/ImportWizard.svelte.d.ts +11 -0
- package/dist/components/JsonSchemaForm.svelte +120 -52
- package/dist/components/JsonSchemaForm.svelte.d.ts +7 -1
- package/dist/components/KanbanBoard.svelte +195 -40
- package/dist/components/KanbanBoard.svelte.d.ts +9 -2
- package/dist/components/Layout.svelte +4 -2
- package/dist/components/LazyAutoForm.svelte +28 -0
- package/dist/components/LazyAutoForm.svelte.d.ts +8 -0
- package/dist/components/LazyAutoTable.svelte +28 -0
- package/dist/components/LazyAutoTable.svelte.d.ts +8 -0
- package/dist/components/LazySettingsPage.svelte +28 -0
- package/dist/components/LazySettingsPage.svelte.d.ts +8 -0
- package/dist/components/LazyShowPage.svelte +28 -0
- package/dist/components/LazyShowPage.svelte.d.ts +8 -0
- package/dist/components/ListPage.svelte +5 -1
- package/dist/components/ListPage.svelte.d.ts +3 -0
- package/dist/components/LoginPage.svelte +1 -1
- package/dist/components/MultiTabKeepAlive.svelte +11 -6
- package/dist/components/NotificationsSettings.svelte +69 -2
- package/dist/components/NotificationsSettings.svelte.d.ts +25 -1
- package/dist/components/PermissionMatrix.svelte +20 -1
- package/dist/components/PivotExportButton.svelte +157 -0
- package/dist/components/PivotExportButton.svelte.d.ts +16 -0
- package/dist/components/PivotTable.svelte +229 -74
- package/dist/components/PivotTable.svelte.d.ts +19 -2
- package/dist/components/RadioGroup.svelte +121 -0
- package/dist/components/RadioGroup.svelte.d.ts +43 -0
- package/dist/components/RangeSlider.svelte +127 -0
- package/dist/components/RangeSlider.svelte.d.ts +22 -0
- package/dist/components/Rate.svelte +121 -0
- package/dist/components/Rate.svelte.d.ts +16 -0
- package/dist/components/RecordDetailDrawer.svelte +4 -2
- package/dist/components/RecordDetailDrawer.svelte.d.ts +2 -0
- package/dist/components/RelationPicker.svelte +341 -0
- package/dist/components/RelationPicker.svelte.d.ts +26 -0
- package/dist/components/ResourceOperationsPage.svelte +18 -18
- package/dist/components/ResourceOperationsPage.svelte.d.ts +2 -2
- package/dist/components/RetryTaskButton.svelte +12 -42
- package/dist/components/SecuritySettings.svelte +29 -5
- package/dist/components/SegmentedControl.svelte +145 -0
- package/dist/components/SegmentedControl.svelte.d.ts +38 -0
- package/dist/components/SettingsPage.svelte +3 -1
- package/dist/components/SettingsPage.svelte.d.ts +1 -0
- package/dist/components/ShowPage.svelte +18 -3
- package/dist/components/ShowPage.svelte.d.ts +3 -0
- package/dist/components/Sidebar.svelte +2 -2
- package/dist/components/Slider.svelte +100 -0
- package/dist/components/Slider.svelte.d.ts +19 -0
- package/dist/components/SpreadsheetView.svelte +1213 -95
- package/dist/components/SpreadsheetView.svelte.d.ts +19 -0
- package/dist/components/TagsInput.svelte +178 -0
- package/dist/components/TagsInput.svelte.d.ts +17 -0
- package/dist/components/TimePicker.svelte +10 -0
- package/dist/components/TimePicker.svelte.d.ts +6 -0
- package/dist/components/TreeSelect.svelte +267 -53
- package/dist/components/TreeSelect.svelte.d.ts +3 -0
- package/dist/components/TreeTable.svelte +484 -82
- package/dist/components/TreeTable.svelte.d.ts +24 -1
- package/dist/components/TwoFactorAuthPage.svelte +85 -48
- package/dist/components/TwoFactorAuthPage.svelte.d.ts +12 -1
- package/dist/components/VirtualTable.svelte +116 -20
- package/dist/components/VirtualTable.svelte.d.ts +5 -0
- package/dist/components/account/CompanyProfilePage.svelte +74 -38
- package/dist/components/account/GetStartedPage.svelte +2 -1
- package/dist/components/account/ImportMembersPage.svelte +22 -167
- package/dist/components/account/ImportMembersPage.svelte.d.ts +4 -1
- package/dist/components/account/MemberDirectory.svelte +156 -0
- package/dist/components/account/MemberDirectory.svelte.d.ts +19 -0
- package/dist/components/account/MembersStarterPage.svelte +9 -55
- package/dist/components/account/MembersStarterPage.svelte.d.ts +5 -1
- package/dist/components/account/SecurityLogPage.svelte +4 -1
- package/dist/components/account/SettingsPlainPage.svelte +7 -38
- package/dist/components/account/SettingsPlainPage.svelte.d.ts +4 -1
- package/dist/components/account/SettingsSidebarPage.svelte +7 -20
- package/dist/components/account/TeamMembersPage.svelte +4 -20
- package/dist/components/account/TeamMembersPage.svelte.d.ts +5 -1
- package/dist/components/account/UserProfilePage.svelte +3 -25
- package/dist/components/account/UserProfilePage.svelte.d.ts +17 -2
- package/dist/components/buttons/ExportButton.svelte +56 -12
- package/dist/components/buttons/ExportButton.svelte.d.ts +11 -0
- package/dist/components/content/ContentPageHeader.svelte +1 -1
- package/dist/components/content/ContentPageShell.svelte +1 -1
- package/dist/components/content/MetricBlock.svelte +1 -1
- package/dist/components/content/PageToolbar.svelte +1 -1
- package/dist/components/content/ReferenceNotice.svelte +5 -0
- package/dist/components/content/ReferenceNotice.svelte.d.ts +18 -0
- package/dist/components/content/SectionHeader.svelte +1 -1
- package/dist/components/content/SettingsFieldRow.svelte +1 -1
- package/dist/components/content/SettingsGroup.svelte +1 -1
- package/dist/components/content/StatusBadge.svelte +1 -1
- package/dist/components/content/WorkspaceLayout.svelte +1 -1
- package/dist/components/export-reference.d.ts +2 -0
- package/dist/components/export-reference.js +9 -0
- package/dist/components/fieldComponentMap.js +2 -5
- package/dist/components/fields/DateField.svelte +12 -13
- package/dist/components/fields/date-display.d.ts +6 -0
- package/dist/components/fields/date-display.js +24 -0
- package/dist/components/network/TeamCrewTablePage.svelte +6 -1
- package/dist/components/network/UserCardsNFTPage.svelte +2 -0
- package/dist/components/profile/PublicProfilePage.svelte +2 -0
- package/dist/components/saved-list-views.d.ts +74 -1
- package/dist/components/saved-list-views.js +191 -3
- package/dist/components/table-contract.d.ts +12 -1
- package/dist/components/task-action.svelte.d.ts +13 -0
- package/dist/components/task-action.svelte.js +73 -0
- package/dist/components/ui/DesignSystemShowcase.svelte +164 -0
- package/dist/components/ui/DesignSystemShowcase.svelte.d.ts +3 -0
- package/dist/components/ui/badge/badge-variants.d.ts +3 -2
- package/dist/components/ui/badge/badge-variants.js +2 -3
- package/dist/components/ui/badge/badge.svelte +3 -0
- package/dist/components/ui/badge/badge.svelte.d.ts +1 -0
- package/dist/components/ui/button/button-variants.d.ts +5 -4
- package/dist/components/ui/button/button-variants.js +7 -5
- package/dist/components/ui/checkbox/checkbox.svelte +5 -1
- package/dist/components/ui/checkbox/checkbox.svelte.d.ts +5 -1
- package/dist/components/ui/input/input.svelte +7 -2
- package/dist/components/ui/input/input.svelte.d.ts +4 -1
- package/dist/components/ui/select/select.svelte +4 -1
- package/dist/components/ui/select/select.svelte.d.ts +2 -1
- package/dist/components/ui/textarea/textarea.svelte +2 -2
- package/dist/components.css +33 -2
- package/dist/date-input-policy.d.ts +4 -0
- package/dist/date-input-policy.js +35 -0
- package/dist/design-system-components.d.ts +100 -0
- package/dist/design-system-components.js +101 -0
- package/dist/design-system.js +1 -1
- package/dist/devtools-bridge.d.ts +80 -0
- package/dist/devtools-bridge.js +92 -0
- package/dist/index.d.ts +37 -2
- package/dist/index.js +23 -2
- package/dist/recipes.d.ts +960 -4
- package/dist/recipes.js +264 -4
- package/dist/tree-table-model.d.ts +24 -0
- package/dist/tree-table-model.js +111 -0
- package/package.json +22 -10
- package/dist/styled-system/css/conditions.js +0 -36
- package/dist/styled-system/css/css.d.ts +0 -22
- package/dist/styled-system/css/css.js +0 -45
- package/dist/styled-system/css/cva.d.ts +0 -6
- package/dist/styled-system/css/cva.js +0 -87
- package/dist/styled-system/css/cx.d.ts +0 -5
- package/dist/styled-system/css/cx.js +0 -15
- package/dist/styled-system/css/index.d.ts +0 -5
- package/dist/styled-system/css/index.js +0 -4
- package/dist/styled-system/css/sva.d.ts +0 -4
- package/dist/styled-system/css/sva.js +0 -46
- package/dist/styled-system/helpers.js +0 -316
- package/dist/styled-system/patterns/aspect-ratio.d.ts +0 -20
- package/dist/styled-system/patterns/aspect-ratio.js +0 -38
- package/dist/styled-system/patterns/bleed.d.ts +0 -21
- package/dist/styled-system/patterns/bleed.js +0 -24
- package/dist/styled-system/patterns/box.d.ts +0 -20
- package/dist/styled-system/patterns/box.js +0 -15
- package/dist/styled-system/patterns/center.d.ts +0 -20
- package/dist/styled-system/patterns/center.js +0 -21
- package/dist/styled-system/patterns/circle.d.ts +0 -20
- package/dist/styled-system/patterns/circle.js +0 -25
- package/dist/styled-system/patterns/container.d.ts +0 -20
- package/dist/styled-system/patterns/container.js +0 -21
- package/dist/styled-system/patterns/cq.d.ts +0 -21
- package/dist/styled-system/patterns/cq.js +0 -21
- package/dist/styled-system/patterns/divider.d.ts +0 -22
- package/dist/styled-system/patterns/divider.js +0 -25
- package/dist/styled-system/patterns/flex.d.ts +0 -26
- package/dist/styled-system/patterns/flex.js +0 -26
- package/dist/styled-system/patterns/float.d.ts +0 -23
- package/dist/styled-system/patterns/float.js +0 -52
- package/dist/styled-system/patterns/grid-item.d.ts +0 -25
- package/dist/styled-system/patterns/grid-item.js +0 -25
- package/dist/styled-system/patterns/grid.d.ts +0 -24
- package/dist/styled-system/patterns/grid.js +0 -27
- package/dist/styled-system/patterns/hstack.d.ts +0 -21
- package/dist/styled-system/patterns/hstack.js +0 -24
- package/dist/styled-system/patterns/index.d.ts +0 -21
- package/dist/styled-system/patterns/index.js +0 -20
- package/dist/styled-system/patterns/link-overlay.d.ts +0 -20
- package/dist/styled-system/patterns/link-overlay.js +0 -24
- package/dist/styled-system/patterns/spacer.d.ts +0 -20
- package/dist/styled-system/patterns/spacer.js +0 -25
- package/dist/styled-system/patterns/square.d.ts +0 -20
- package/dist/styled-system/patterns/square.js +0 -24
- package/dist/styled-system/patterns/stack.d.ts +0 -23
- package/dist/styled-system/patterns/stack.js +0 -24
- package/dist/styled-system/patterns/visually-hidden.d.ts +0 -20
- package/dist/styled-system/patterns/visually-hidden.js +0 -18
- package/dist/styled-system/patterns/vstack.d.ts +0 -21
- package/dist/styled-system/patterns/vstack.js +0 -24
- package/dist/styled-system/patterns/wrap.d.ts +0 -24
- package/dist/styled-system/patterns/wrap.js +0 -25
- package/dist/styled-system/recipes/content-header.d.ts +0 -31
- package/dist/styled-system/recipes/content-header.js +0 -65
- package/dist/styled-system/recipes/content-page.d.ts +0 -34
- package/dist/styled-system/recipes/content-page.js +0 -43
- package/dist/styled-system/recipes/create-recipe.js +0 -82
- package/dist/styled-system/recipes/index.d.ts +0 -17
- package/dist/styled-system/recipes/index.js +0 -16
- package/dist/styled-system/recipes/metric-block.d.ts +0 -34
- package/dist/styled-system/recipes/metric-block.js +0 -76
- package/dist/styled-system/recipes/product-list.d.ts +0 -31
- package/dist/styled-system/recipes/product-list.js +0 -73
- package/dist/styled-system/recipes/product-section.d.ts +0 -31
- package/dist/styled-system/recipes/product-section.js +0 -49
- package/dist/styled-system/recipes/product-settings-row.d.ts +0 -34
- package/dist/styled-system/recipes/product-settings-row.js +0 -58
- package/dist/styled-system/recipes/product-settings.d.ts +0 -31
- package/dist/styled-system/recipes/product-settings.js +0 -57
- package/dist/styled-system/recipes/product-status.d.ts +0 -34
- package/dist/styled-system/recipes/product-status.js +0 -45
- package/dist/styled-system/recipes/product-toolbar.d.ts +0 -31
- package/dist/styled-system/recipes/product-toolbar.js +0 -41
- package/dist/styled-system/recipes/product-workspace.d.ts +0 -34
- package/dist/styled-system/recipes/product-workspace.js +0 -58
- package/dist/styled-system/recipes/surface-metric.d.ts +0 -38
- package/dist/styled-system/recipes/surface-metric.js +0 -63
- package/dist/styled-system/recipes/surface-table.d.ts +0 -34
- package/dist/styled-system/recipes/surface-table.js +0 -62
- package/dist/styled-system/recipes/ui-badge.d.ts +0 -31
- package/dist/styled-system/recipes/ui-badge.js +0 -38
- package/dist/styled-system/recipes/ui-button.d.ts +0 -32
- package/dist/styled-system/recipes/ui-button.js +0 -43
- package/dist/styled-system/recipes/ui-input.d.ts +0 -31
- package/dist/styled-system/recipes/ui-input.js +0 -49
- package/dist/styled-system/recipes/ui-textarea.d.ts +0 -31
- package/dist/styled-system/recipes/ui-textarea.js +0 -24
- package/dist/styled-system/styles.css +0 -1257
- package/dist/styled-system/tokens/index.d.ts +0 -9
- package/dist/styled-system/tokens/index.js +0 -268
- package/dist/styled-system/tokens/tokens.d.ts +0 -36
- package/dist/styled-system/types/composition.d.ts +0 -227
- package/dist/styled-system/types/conditions.d.ts +0 -236
- package/dist/styled-system/types/csstype.d.ts +0 -22570
- package/dist/styled-system/types/index.d.ts +0 -6
- package/dist/styled-system/types/parts.d.ts +0 -8
- package/dist/styled-system/types/pattern.d.ts +0 -78
- package/dist/styled-system/types/prop-type.d.ts +0 -223
- package/dist/styled-system/types/recipe.d.ts +0 -181
- package/dist/styled-system/types/selectors.d.ts +0 -59
- package/dist/styled-system/types/static-css.d.ts +0 -56
- package/dist/styled-system/types/style-props.d.ts +0 -8088
- package/dist/styled-system/types/system-types.d.ts +0 -151
- package/dist/styles/recipes.css +0 -1262
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { type ResourceRendering } from '../rendering/index.js';
|
|
2
2
|
import type { AutoTableGridState } from './auto-table-grid.js';
|
|
3
|
+
import { type ExportFormat, type TaskProvider } from '@svadmin/core';
|
|
4
|
+
import { type BatchSelection } from './table-contract';
|
|
3
5
|
import type { BaseRecord, FieldDefinition, Sort } from '@svadmin/core';
|
|
6
|
+
import { type SavedListViewProvider } from './saved-list-views.js';
|
|
4
7
|
import type { Snippet } from 'svelte';
|
|
5
8
|
interface Props {
|
|
6
9
|
resourceName: string;
|
|
@@ -8,6 +11,8 @@ interface Props {
|
|
|
8
11
|
/** 可选主体渲染器:不替换资源、权限、工具栏和持久化逻辑。 */
|
|
9
12
|
gridBody?: Snippet<[AutoTableGridState]>;
|
|
10
13
|
selectable?: boolean;
|
|
14
|
+
/** 仅在业务回调支持按查询执行服务端命令时启用。 */
|
|
15
|
+
allowSelectAllMatching?: boolean;
|
|
11
16
|
density?: 'compact' | 'comfortable';
|
|
12
17
|
showHeader?: boolean;
|
|
13
18
|
title?: string;
|
|
@@ -45,9 +50,10 @@ interface Props {
|
|
|
45
50
|
};
|
|
46
51
|
/** Externally controlled sorters */
|
|
47
52
|
sorters?: Sort[];
|
|
48
|
-
/**
|
|
53
|
+
/** 批量操作必须按 selection.scope 区分明确 ID 与全部查询结果。 */
|
|
49
54
|
batchActions?: Snippet<[{
|
|
50
55
|
selectedIds: (string | number)[];
|
|
56
|
+
selection: BatchSelection;
|
|
51
57
|
}]>;
|
|
52
58
|
/** Summary row rendered at table footer */
|
|
53
59
|
summary?: Snippet<[{
|
|
@@ -56,6 +62,16 @@ interface Props {
|
|
|
56
62
|
visibleColumnsCount: number;
|
|
57
63
|
}]>;
|
|
58
64
|
deleteVariables?: unknown;
|
|
65
|
+
/** 团队/系统视图;写入需要 Provider 能力和显式可写标记。 */
|
|
66
|
+
savedViewProvider?: SavedListViewProvider;
|
|
67
|
+
/** 显式允许唯一的远程默认视图在无 URL/个人活动视图时初始化页面。 */
|
|
68
|
+
applyRemoteDefaultView?: boolean;
|
|
69
|
+
/** 提供任务名后按当前筛选/排序提交全量导出;未提供时仍导出当前已加载页。 */
|
|
70
|
+
exportTaskName?: string;
|
|
71
|
+
exportTaskProvider?: TaskProvider;
|
|
72
|
+
exportTaskIdempotencyKey?: string;
|
|
73
|
+
exportFormat?: ExportFormat;
|
|
74
|
+
exportMaxItemCount?: number;
|
|
59
75
|
}
|
|
60
76
|
declare const AutoTable: import("svelte").Component<Props, {}, "">;
|
|
61
77
|
type AutoTable = ReturnType<typeof AutoTable>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { ResourceRendering } from '../rendering/index.js';
|
|
3
3
|
import { definedOptions } from '@svadmin/core/options';
|
|
4
|
+
import type { Snippet } from 'svelte';
|
|
4
5
|
|
|
5
6
|
import { useCan, useNavigation, type FieldDefinition } from '@svadmin/core';
|
|
6
7
|
import { useRecordDetail } from './record-detail.svelte';
|
|
@@ -17,12 +18,14 @@
|
|
|
17
18
|
open = $bindable(false),
|
|
18
19
|
recordId,
|
|
19
20
|
onClose,
|
|
21
|
+
extraSections,
|
|
20
22
|
}: {
|
|
21
23
|
resourceName: string;
|
|
22
24
|
rendering?: ResourceRendering | undefined;
|
|
23
25
|
open?: boolean;
|
|
24
26
|
recordId: string | number;
|
|
25
27
|
onClose?: () => void;
|
|
28
|
+
extraSections?: Snippet;
|
|
26
29
|
} = $props();
|
|
27
30
|
|
|
28
31
|
const i18n = useTranslation();
|
|
@@ -149,6 +152,12 @@
|
|
|
149
152
|
</div>
|
|
150
153
|
{/if}
|
|
151
154
|
|
|
155
|
+
{#if extraSections && canReadRecord && query.isSuccess}
|
|
156
|
+
<section data-svadmin-detail-extensions aria-label={i18n.t('common.additionalDetails')}>
|
|
157
|
+
{@render extraSections()}
|
|
158
|
+
</section>
|
|
159
|
+
{/if}
|
|
160
|
+
|
|
152
161
|
{#snippet footer()}
|
|
153
162
|
{#if canReadRecord && query.isSuccess}
|
|
154
163
|
<Button type="button" variant="outline" onclick={() => navigateTo('show')}>
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { ResourceRendering } from '../rendering/index.js';
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
2
3
|
type $$ComponentProps = {
|
|
3
4
|
resourceName: string;
|
|
4
5
|
rendering?: ResourceRendering | undefined;
|
|
5
6
|
open?: boolean;
|
|
6
7
|
recordId: string | number;
|
|
7
8
|
onClose?: () => void;
|
|
9
|
+
extraSections?: Snippet;
|
|
8
10
|
};
|
|
9
11
|
declare const BoundRecordDetailDrawer: import("svelte").Component<$$ComponentProps, {}, "open">;
|
|
10
12
|
type BoundRecordDetailDrawer = ReturnType<typeof BoundRecordDetailDrawer>;
|
|
@@ -17,6 +17,12 @@
|
|
|
17
17
|
|
|
18
18
|
interface Crumb { label: string; href?: string; }
|
|
19
19
|
|
|
20
|
+
// 仅复用 AdminApp 明确支持的成员页别名,宿主精确菜单项始终优先。
|
|
21
|
+
const memberAliases: Readonly<Record<string, string>> = {
|
|
22
|
+
'/account/members-starter': '/account/members/members-starter',
|
|
23
|
+
'/account/team-members': '/account/members/team-members',
|
|
24
|
+
};
|
|
25
|
+
|
|
20
26
|
function normalizePath(path: string): string {
|
|
21
27
|
const normalized = path.replace(/^#/, '').replace(/[?#].*$/, '').replace(/\/$/, '');
|
|
22
28
|
return normalized || '/';
|
|
@@ -38,7 +44,11 @@
|
|
|
38
44
|
const currentPathname = normalizePath(getPath());
|
|
39
45
|
if (currentPathname === '/') return result;
|
|
40
46
|
|
|
41
|
-
const
|
|
47
|
+
const canonicalPath = memberAliases[currentPathname];
|
|
48
|
+
const menuTrail = menu
|
|
49
|
+
? findMenuTrail(menu, currentPathname)
|
|
50
|
+
?? (canonicalPath ? findMenuTrail(menu, canonicalPath) : undefined)
|
|
51
|
+
: undefined;
|
|
42
52
|
if (menuTrail) {
|
|
43
53
|
for (const item of menuTrail) {
|
|
44
54
|
result.push(definedOptions({
|
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { captureAdminContext, queryKeyMatches, withValidatedTaskProvider } from '@svadmin/core';
|
|
3
|
-
import { useQueryClient } from '@tanstack/svelte-query';
|
|
4
2
|
import { useTranslation } from '@svadmin/core/i18n';
|
|
5
3
|
|
|
6
4
|
import { Button } from './ui/button/index.js';
|
|
7
5
|
import { Loader2, Ban } from '@lucide/svelte';
|
|
6
|
+
import { createTaskAction } from './task-action.svelte.js';
|
|
8
7
|
|
|
9
8
|
const i18n = useTranslation();
|
|
10
|
-
const adminContext = captureAdminContext();
|
|
11
9
|
|
|
12
10
|
let {
|
|
13
11
|
taskId,
|
|
14
|
-
taskProvider
|
|
12
|
+
taskProvider,
|
|
15
13
|
onSuccess,
|
|
16
14
|
onError,
|
|
17
15
|
disabled = false,
|
|
@@ -29,46 +27,18 @@
|
|
|
29
27
|
children?: import('svelte').Snippet;
|
|
30
28
|
}>();
|
|
31
29
|
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
try {
|
|
41
|
-
const provider = withValidatedTaskProvider(taskProvider);
|
|
42
|
-
if (!provider.cancel) return;
|
|
43
|
-
await provider.cancel(scopedTaskId);
|
|
44
|
-
await Promise.all([
|
|
45
|
-
queryClient.invalidateQueries({
|
|
46
|
-
predicate: (query) => queryKeyMatches(query.queryKey, {
|
|
47
|
-
...scope,
|
|
48
|
-
kind: 'task',
|
|
49
|
-
action: 'list',
|
|
50
|
-
}),
|
|
51
|
-
}),
|
|
52
|
-
queryClient.invalidateQueries({
|
|
53
|
-
predicate: (query) => queryKeyMatches(query.queryKey, {
|
|
54
|
-
...scope,
|
|
55
|
-
kind: 'task',
|
|
56
|
-
action: 'one',
|
|
57
|
-
id: scopedTaskId,
|
|
58
|
-
}),
|
|
59
|
-
}),
|
|
60
|
-
]);
|
|
61
|
-
onSuccess?.();
|
|
62
|
-
} catch (error) {
|
|
63
|
-
onError?.(error);
|
|
64
|
-
} finally {
|
|
65
|
-
pending = false;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
30
|
+
const action = createTaskAction({
|
|
31
|
+
action: 'cancel',
|
|
32
|
+
get taskId() { return taskId; },
|
|
33
|
+
get provider() { return taskProvider; },
|
|
34
|
+
get disabled() { return disabled; },
|
|
35
|
+
get onSuccess() { return onSuccess; },
|
|
36
|
+
get onError() { return onError; },
|
|
37
|
+
});
|
|
68
38
|
</script>
|
|
69
39
|
|
|
70
|
-
<Button {variant} {size} onclick={
|
|
71
|
-
{#if pending}
|
|
40
|
+
<Button {variant} {size} onclick={action.run} disabled={!action.available || action.pending}>
|
|
41
|
+
{#if action.pending}
|
|
72
42
|
<Loader2 class="svadmin-u-82cc6c6581cd svadmin-u-7fc7f732bf7e svadmin-u-bf600f8e029c svadmin-u-afbdd13a380e" />
|
|
73
43
|
{:else}
|
|
74
44
|
<Ban class="svadmin-u-82cc6c6581cd svadmin-u-7fc7f732bf7e svadmin-u-bf600f8e029c" />
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn } from '../utils.js';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
value?: string;
|
|
6
|
+
name?: string;
|
|
7
|
+
id?: string;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
required?: boolean;
|
|
10
|
+
invalid?: boolean;
|
|
11
|
+
describedby?: string;
|
|
12
|
+
ariaLabel?: string;
|
|
13
|
+
class?: string;
|
|
14
|
+
onchange?: (value: string) => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let {
|
|
18
|
+
value = $bindable(''),
|
|
19
|
+
name,
|
|
20
|
+
id,
|
|
21
|
+
disabled = false,
|
|
22
|
+
required = false,
|
|
23
|
+
invalid = false,
|
|
24
|
+
describedby,
|
|
25
|
+
ariaLabel,
|
|
26
|
+
class: className = '',
|
|
27
|
+
onchange,
|
|
28
|
+
}: Props = $props();
|
|
29
|
+
|
|
30
|
+
function handleInput(event: Event): void {
|
|
31
|
+
const input = event.currentTarget;
|
|
32
|
+
if (!(input instanceof HTMLInputElement) || disabled) return;
|
|
33
|
+
value = input.value;
|
|
34
|
+
onchange?.(input.value);
|
|
35
|
+
}
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
<input
|
|
39
|
+
class={cn('svadmin-color-picker', className)}
|
|
40
|
+
{id}
|
|
41
|
+
{name}
|
|
42
|
+
type="color"
|
|
43
|
+
value={value}
|
|
44
|
+
{disabled}
|
|
45
|
+
{required}
|
|
46
|
+
aria-label={ariaLabel}
|
|
47
|
+
aria-invalid={invalid || undefined}
|
|
48
|
+
aria-describedby={describedby}
|
|
49
|
+
oninput={handleInput}
|
|
50
|
+
/>
|
|
51
|
+
|
|
52
|
+
<style>
|
|
53
|
+
.svadmin-color-picker {
|
|
54
|
+
display: block;
|
|
55
|
+
inline-size: 40px;
|
|
56
|
+
block-size: 32px;
|
|
57
|
+
padding: 2px;
|
|
58
|
+
border: 1px solid var(--border);
|
|
59
|
+
border-radius: 6px;
|
|
60
|
+
background: var(--background);
|
|
61
|
+
cursor: pointer;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.svadmin-color-picker:focus-visible {
|
|
65
|
+
outline: 2px solid var(--ring);
|
|
66
|
+
outline-offset: 2px;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.svadmin-color-picker:disabled {
|
|
70
|
+
cursor: not-allowed;
|
|
71
|
+
opacity: 0.5;
|
|
72
|
+
}
|
|
73
|
+
</style>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
value?: string;
|
|
3
|
+
name?: string;
|
|
4
|
+
id?: string;
|
|
5
|
+
disabled?: boolean;
|
|
6
|
+
required?: boolean;
|
|
7
|
+
invalid?: boolean;
|
|
8
|
+
describedby?: string;
|
|
9
|
+
ariaLabel?: string;
|
|
10
|
+
class?: string;
|
|
11
|
+
onchange?: (value: string) => void;
|
|
12
|
+
}
|
|
13
|
+
declare const ColorPicker: import("svelte").Component<Props, {}, "value">;
|
|
14
|
+
type ColorPicker = ReturnType<typeof ColorPicker>;
|
|
15
|
+
export default ColorPicker;
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
import { definedReactiveOptions } from '@svadmin/core/options';
|
|
3
3
|
|
|
4
4
|
import { useTranslation } from '@svadmin/core/i18n';
|
|
5
|
-
import { useResourceContract, useSelect } from '@svadmin/core';
|
|
5
|
+
import { captureAdminContext, captureAuthSession, useResourceContract, useSelect } from '@svadmin/core';
|
|
6
|
+
import { onDestroy, untrack } from 'svelte';
|
|
6
7
|
import type { BaseRecord, Filter } from '@svadmin/core';
|
|
7
8
|
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
8
9
|
import type { ButtonProps } from './ui/button/index.js';
|
|
@@ -31,6 +32,7 @@
|
|
|
31
32
|
searchable?: boolean;
|
|
32
33
|
multiple?: boolean;
|
|
33
34
|
onSearch?: (value: string) => Filter[];
|
|
35
|
+
fetchSize?: number;
|
|
34
36
|
id?: string;
|
|
35
37
|
class?: string;
|
|
36
38
|
}
|
|
@@ -41,21 +43,51 @@
|
|
|
41
43
|
onchange,
|
|
42
44
|
optionLabel = 'title',
|
|
43
45
|
optionValue = 'id',
|
|
44
|
-
placeholder
|
|
46
|
+
placeholder,
|
|
45
47
|
searchable = true,
|
|
46
48
|
multiple = false,
|
|
47
49
|
onSearch,
|
|
50
|
+
fetchSize = 50,
|
|
48
51
|
disabled = false,
|
|
49
52
|
...restProps
|
|
50
53
|
}: Props = $props();
|
|
51
54
|
|
|
52
55
|
const buttonRestProps = $derived(restProps as Omit<ButtonProps, 'children' | 'variant' | 'size'>);
|
|
56
|
+
let open = $state(false);
|
|
57
|
+
let searchInputValue = $state('');
|
|
58
|
+
let page = $state(1);
|
|
59
|
+
let appliedSearch = $state('');
|
|
60
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
61
|
+
onDestroy(() => clearTimeout(timer));
|
|
62
|
+
const normalizedFetchSize = $derived(
|
|
63
|
+
Number.isSafeInteger(fetchSize) && fetchSize >= 10 && fetchSize <= 200 ? fetchSize : 50,
|
|
64
|
+
);
|
|
53
65
|
|
|
54
66
|
const binding = useResourceContract(() => resource);
|
|
67
|
+
const context = captureAdminContext();
|
|
68
|
+
const sourceScope = $derived({
|
|
69
|
+
contract: binding.resource, provider: context.providers?.[binding.dataProviderName],
|
|
70
|
+
meta: JSON.stringify(binding.meta), tenant: context.tenantCacheKey?.__svadminTenant,
|
|
71
|
+
auth: context.authProvider, session: captureAuthSession(context.authProvider).cacheKey,
|
|
72
|
+
access: context.accessControlProvider, router: context.routerProvider,
|
|
73
|
+
optionLabel, optionValue, onSearch, searchable, normalizedFetchSize,
|
|
74
|
+
});
|
|
75
|
+
type Option = { value: string | number; label: string };
|
|
76
|
+
let loaded = $state.raw<{ scope: typeof sourceScope; search: string; options: Option[] }>();
|
|
55
77
|
const defaultSearch = $derived.by(() => {
|
|
56
78
|
const field = optionLabel;
|
|
57
79
|
return (v: string): Filter[] => v ? [{ field, operator: 'contains', value: v }] : [];
|
|
58
80
|
});
|
|
81
|
+
const searchFilters = $derived.by(() => {
|
|
82
|
+
try {
|
|
83
|
+
return {
|
|
84
|
+
filters: appliedSearch && (onSearch || searchable)
|
|
85
|
+
? (onSearch ?? defaultSearch)(appliedSearch)
|
|
86
|
+
: [],
|
|
87
|
+
invalid: false,
|
|
88
|
+
};
|
|
89
|
+
} catch { return { filters: [], invalid: true }; }
|
|
90
|
+
});
|
|
59
91
|
const select = useSelect(definedReactiveOptions({
|
|
60
92
|
get resource() { return binding.resource; },
|
|
61
93
|
get dataProviderName() { return binding.dataProviderName; },
|
|
@@ -69,33 +101,62 @@
|
|
|
69
101
|
};
|
|
70
102
|
},
|
|
71
103
|
get projectionKey() { return `combobox:${optionLabel}:${optionValue}`; },
|
|
104
|
+
get fetchSize() { return normalizedFetchSize; },
|
|
105
|
+
get pagination() { return { current: page, pageSize: normalizedFetchSize }; },
|
|
72
106
|
get defaultValue() {
|
|
73
107
|
if (Array.isArray(value)) return value;
|
|
74
|
-
return value === null || value === undefined ? [] : [value];
|
|
108
|
+
return value === null || value === undefined || value === '' ? [] : [value];
|
|
75
109
|
},
|
|
76
|
-
get
|
|
110
|
+
get filters() { return searchFilters.filters; },
|
|
111
|
+
get queryOptions() { return { enabled: !searchFilters.invalid }; },
|
|
77
112
|
}));
|
|
78
113
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
void binding.dataProviderName;
|
|
84
|
-
void binding.meta;
|
|
85
|
-
searchInputValue = '';
|
|
114
|
+
$effect.pre(() => {
|
|
115
|
+
void sourceScope;
|
|
116
|
+
clearTimeout(timer);
|
|
117
|
+
searchInputValue = appliedSearch = '';
|
|
86
118
|
open = false;
|
|
119
|
+
page = 1;
|
|
120
|
+
loaded = undefined;
|
|
121
|
+
});
|
|
122
|
+
$effect(() => {
|
|
123
|
+
const data = select.query.data;
|
|
124
|
+
if (!data || select.query.isFetching || select.isError || searchFilters.invalid) return;
|
|
125
|
+
const scope = sourceScope;
|
|
126
|
+
const search = appliedSearch;
|
|
127
|
+
const previous = untrack(() => loaded);
|
|
128
|
+
const options = new Map<string | number, Option>(
|
|
129
|
+
page > 1 && previous?.scope === scope && previous.search === search
|
|
130
|
+
? previous.options.map(option => [option.value, option]) : [],
|
|
131
|
+
);
|
|
132
|
+
for (const option of data.options) options.set(option.value, option);
|
|
133
|
+
loaded = { scope, search, options: [...options.values()] };
|
|
87
134
|
});
|
|
88
135
|
|
|
89
136
|
const selectedValues = $derived(
|
|
90
137
|
Array.isArray(value) ? value : value === null || value === undefined ? [] : [value],
|
|
91
138
|
);
|
|
139
|
+
const availableOptions = $derived.by(() => {
|
|
140
|
+
if (select.isError || searchFilters.invalid) return [];
|
|
141
|
+
const options = new Map<string | number, Option>(
|
|
142
|
+
loaded?.scope === sourceScope && loaded.search === appliedSearch
|
|
143
|
+
? loaded.options.map(option => [option.value, option]) : [],
|
|
144
|
+
);
|
|
145
|
+
for (const option of select.options) options.set(option.value, option);
|
|
146
|
+
return [...options.values()];
|
|
147
|
+
});
|
|
148
|
+
const totalOptions = $derived(select.query.data?.total ?? availableOptions.length);
|
|
149
|
+
const hasMore = $derived(totalOptions > page * normalizedFetchSize && !select.isError
|
|
150
|
+
&& !searchFilters.invalid && searchInputValue === appliedSearch);
|
|
151
|
+
const invalidOptions = $derived(select.isError || searchFilters.invalid);
|
|
92
152
|
const selectedLabels = $derived(selectedValues.flatMap(selectedValue => {
|
|
93
|
-
const option =
|
|
153
|
+
const option = availableOptions.find(candidate => candidate.value === selectedValue);
|
|
94
154
|
return option ? [option.label] : [];
|
|
95
155
|
}));
|
|
96
156
|
const selectedLabel = $derived(selectedLabels.join(', '));
|
|
97
157
|
|
|
98
158
|
function handleSelect(optValue: string | number) {
|
|
159
|
+
if (disabled || invalidOptions || select.isFetching || searchInputValue !== appliedSearch) return;
|
|
99
160
|
if (multiple) {
|
|
100
161
|
const next = selectedValues.includes(optValue)
|
|
101
162
|
? selectedValues.filter(selectedValue => selectedValue !== optValue)
|
|
@@ -105,23 +166,43 @@
|
|
|
105
166
|
}
|
|
106
167
|
onchange?.(optValue === value ? null : optValue);
|
|
107
168
|
open = false;
|
|
108
|
-
|
|
109
|
-
select.onSearchChange('');
|
|
169
|
+
resetSearch();
|
|
110
170
|
}
|
|
111
171
|
|
|
112
172
|
function handleClear(e: Event) {
|
|
113
173
|
e.stopPropagation();
|
|
174
|
+
if (disabled) return;
|
|
114
175
|
onchange?.(multiple ? [] : null);
|
|
115
176
|
open = false;
|
|
116
|
-
|
|
117
|
-
|
|
177
|
+
resetSearch();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function resetSearch(): void {
|
|
181
|
+
clearTimeout(timer);
|
|
182
|
+
searchInputValue = appliedSearch = '';
|
|
183
|
+
page = 1;
|
|
184
|
+
loaded = undefined;
|
|
118
185
|
}
|
|
119
186
|
|
|
120
187
|
function handleSearchInput(e: Event) {
|
|
121
188
|
if (!(e.currentTarget instanceof HTMLInputElement)) return;
|
|
122
189
|
const v = e.currentTarget.value;
|
|
123
190
|
searchInputValue = v;
|
|
124
|
-
|
|
191
|
+
clearTimeout(timer);
|
|
192
|
+
const scope = sourceScope;
|
|
193
|
+
const apply = () => {
|
|
194
|
+
if (scope !== sourceScope || disabled) return;
|
|
195
|
+
appliedSearch = v;
|
|
196
|
+
page = 1;
|
|
197
|
+
loaded = undefined;
|
|
198
|
+
};
|
|
199
|
+
if (!v) apply();
|
|
200
|
+
else timer = setTimeout(apply, 300);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function loadMore(): void {
|
|
204
|
+
if (disabled || select.isFetching || !hasMore) return;
|
|
205
|
+
page += 1;
|
|
125
206
|
}
|
|
126
207
|
</script>
|
|
127
208
|
|
|
@@ -138,12 +219,12 @@
|
|
|
138
219
|
aria-expanded={open}
|
|
139
220
|
aria-multiselectable={multiple || undefined}
|
|
140
221
|
aria-busy={select.isFetching}
|
|
141
|
-
aria-invalid={
|
|
222
|
+
aria-invalid={invalidOptions || restProps['aria-invalid']}
|
|
142
223
|
>
|
|
143
224
|
{#if selectedLabel}
|
|
144
225
|
<span class="svadmin-u-f283ea9bea0e">{selectedLabel}</span>
|
|
145
226
|
{:else}
|
|
146
|
-
<span class="svadmin-u-bfa603190748">{placeholder}</span>
|
|
227
|
+
<span class="svadmin-u-bfa603190748">{placeholder ?? i18n.t('field.selectPlaceholder')}</span>
|
|
147
228
|
{/if}
|
|
148
229
|
<ChevronsUpDown class="svadmin-u-fb56d9cff341 svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3 svadmin-u-012fbd121f37 svadmin-u-0b8c506a0596" />
|
|
149
230
|
</Button>
|
|
@@ -154,9 +235,9 @@
|
|
|
154
235
|
</Button>
|
|
155
236
|
{/if}
|
|
156
237
|
</div>
|
|
157
|
-
{#if
|
|
238
|
+
{#if invalidOptions}
|
|
158
239
|
<div role="alert" class="select-error">
|
|
159
|
-
<span>
|
|
240
|
+
<span>{i18n.t('select.loadFailed')}</span>
|
|
160
241
|
<Button type="button" variant="ghost" size="icon-sm" disabled={disabled || select.isFetching}
|
|
161
242
|
title={i18n.t('common.retry')} aria-label={i18n.t('common.retry')} onclick={() => select.refetch()}>
|
|
162
243
|
<RotateCw class="svadmin-u-6a60c09e6aaa svadmin-u-9cea05671a29" />
|
|
@@ -164,7 +245,7 @@
|
|
|
164
245
|
</div>
|
|
165
246
|
{/if}
|
|
166
247
|
|
|
167
|
-
{#if open}
|
|
248
|
+
{#if open && !disabled}
|
|
168
249
|
<div class="svadmin-u-da4dbfbc4fdc svadmin-u-181b286668b5 svadmin-u-b6b02c0ebef6 svadmin-u-6da6a3c3f741 svadmin-u-421ac2be5045 svadmin-u-ca6bcd4b6f3f svadmin-u-e541d86d1ec8 svadmin-u-06bbb43166db">
|
|
169
250
|
<Command.Root shouldFilter={false} class="svadmin-u-60fbb7713999 svadmin-u-8dddea0773ed svadmin-u-2cd02d11d1af svadmin-u-421ac2be5045 svadmin-u-e541d86d1ec8 svadmin-u-9b13e8ae5c9c">
|
|
170
251
|
{#if searchable}
|
|
@@ -173,7 +254,7 @@
|
|
|
173
254
|
<Command.Input
|
|
174
255
|
bind:value={searchInputValue}
|
|
175
256
|
oninput={handleSearchInput}
|
|
176
|
-
placeholder=
|
|
257
|
+
placeholder={i18n.t('common.search')}
|
|
177
258
|
class="svadmin-u-60fbb7713999 svadmin-u-e7a768f922d2 svadmin-u-6da6a3c3f741 svadmin-u-7f19cdf4c5bb svadmin-u-03b4dd7f172b svadmin-u-fc7473ca09eb svadmin-u-df37b1fd9495 svadmin-u-9c24ab70af61"
|
|
178
259
|
/>
|
|
179
260
|
</div>
|
|
@@ -187,14 +268,15 @@
|
|
|
187
268
|
{#each Array(3) as _, _i (_i)}
|
|
188
269
|
<div class="svadmin-u-d5eab218aa34 svadmin-u-ec0091ee009b"><Skeleton class="svadmin-u-cd0d9c512cdc svadmin-u-6da6a3c3f741" /></div>
|
|
189
270
|
{/each}
|
|
190
|
-
{:else if !
|
|
191
|
-
{#if !
|
|
271
|
+
{:else if !invalidOptions}
|
|
272
|
+
{#if !availableOptions.length}
|
|
192
273
|
<Command.Empty class="svadmin-u-cb11fec3bb46 svadmin-u-ca6bf63030aa svadmin-u-359090c2d529 svadmin-u-bfa603190748">
|
|
193
|
-
|
|
274
|
+
{i18n.t('select.noResults')}
|
|
194
275
|
</Command.Empty>
|
|
195
276
|
{/if}
|
|
196
|
-
{#each
|
|
277
|
+
{#each availableOptions as opt (`${typeof opt.value}:${opt.value}`)}
|
|
197
278
|
<Command.Item
|
|
279
|
+
disabled={select.isFetching || searchInputValue !== appliedSearch}
|
|
198
280
|
value={`${typeof opt.value}:${opt.value}`}
|
|
199
281
|
onSelect={() => handleSelect(opt.value)}
|
|
200
282
|
class="svadmin-u-d89972fe17d6 svadmin-u-60fbb7713999 svadmin-u-50ca6ba56aa3 svadmin-u-7f6912283f11 svadmin-u-3960ffc248d9 svadmin-u-36d4469299aa svadmin-u-d5eab218aa34 svadmin-u-ec0091ee009b svadmin-u-fc7473ca09eb svadmin-u-df37b1fd9495 svadmin-u-99afb1cc3b47 svadmin-u-529780e25268"
|
|
@@ -205,6 +287,11 @@
|
|
|
205
287
|
{/each}
|
|
206
288
|
{/if}
|
|
207
289
|
</Command.List>
|
|
290
|
+
{#if hasMore}
|
|
291
|
+
<Button type="button" variant="ghost" size="sm" disabled={select.isFetching} onclick={loadMore}>
|
|
292
|
+
{i18n.t('select.loadMore')}
|
|
293
|
+
</Button>
|
|
294
|
+
{/if}
|
|
208
295
|
</Command.Root>
|
|
209
296
|
</div>
|
|
210
297
|
{/if}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Loader2, RefreshCw } from '@lucide/svelte';
|
|
3
|
+
import {
|
|
4
|
+
DashboardContractError, captureAdminContext, captureAuthSession, createEnterpriseRequestContext,
|
|
5
|
+
decodeDashboardQuery, decodeDashboardSnapshot,
|
|
6
|
+
} from '@svadmin/core';
|
|
7
|
+
import type { DashboardProvider, DashboardSnapshot, EnterpriseProviderRequestContext } from '@svadmin/core';
|
|
8
|
+
import { useTranslation } from '@svadmin/core/i18n';
|
|
9
|
+
import { onDestroy, untrack } from 'svelte';
|
|
10
|
+
import MetricStrip, { type MetricStripItem } from './MetricStrip.svelte';
|
|
11
|
+
import { Button } from './ui/button/index.js';
|
|
12
|
+
|
|
13
|
+
interface Props {
|
|
14
|
+
provider: DashboardProvider;
|
|
15
|
+
dashboardId: string;
|
|
16
|
+
requestContext?: EnterpriseProviderRequestContext;
|
|
17
|
+
filters?: readonly { field: string; value: string | number | boolean | null }[];
|
|
18
|
+
title?: string;
|
|
19
|
+
}
|
|
20
|
+
let { provider, dashboardId, requestContext, filters = [], title }: Props = $props();
|
|
21
|
+
const context = captureAdminContext();
|
|
22
|
+
const i18n = useTranslation();
|
|
23
|
+
let snapshot = $state<DashboardSnapshot>();
|
|
24
|
+
let loading = $state(false);
|
|
25
|
+
let error = $state<'load' | 'invalid'>();
|
|
26
|
+
let epoch = 0;
|
|
27
|
+
let mounted = true;
|
|
28
|
+
let generatedContext: EnterpriseProviderRequestContext | undefined;
|
|
29
|
+
onDestroy(() => { mounted = false; });
|
|
30
|
+
|
|
31
|
+
function resolveContext(): EnterpriseProviderRequestContext {
|
|
32
|
+
if (requestContext) return requestContext;
|
|
33
|
+
const tenant = context.tenantCacheKey?.__svadminTenant;
|
|
34
|
+
if (typeof tenant !== 'string' && typeof tenant !== 'number') throw new Error('Missing tenant context');
|
|
35
|
+
if (generatedContext?.tenantId === tenant) return generatedContext;
|
|
36
|
+
generatedContext = createEnterpriseRequestContext({ tenantId: tenant });
|
|
37
|
+
return generatedContext;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function load(): Promise<void> {
|
|
41
|
+
const currentEpoch = ++epoch;
|
|
42
|
+
loading = true;
|
|
43
|
+
error = undefined;
|
|
44
|
+
try {
|
|
45
|
+
const request = decodeDashboardQuery({
|
|
46
|
+
dashboardId: dashboardId.trim(),
|
|
47
|
+
...(filters.length ? { filters: filters.map(filter => ({ ...filter })) } : {}),
|
|
48
|
+
});
|
|
49
|
+
const result = decodeDashboardSnapshot(await provider.get(resolveContext(), request));
|
|
50
|
+
if (!mounted || currentEpoch !== epoch) return;
|
|
51
|
+
snapshot = result;
|
|
52
|
+
} catch (cause) {
|
|
53
|
+
if (!mounted || currentEpoch !== epoch) return;
|
|
54
|
+
snapshot = undefined;
|
|
55
|
+
error = cause instanceof DashboardContractError ? 'invalid' : 'load';
|
|
56
|
+
} finally {
|
|
57
|
+
if (mounted && currentEpoch === epoch) loading = false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const metricItems = $derived<MetricStripItem[]>(snapshot?.widgets.flatMap(widget =>
|
|
62
|
+
widget.kind === 'metric' ? widget.metrics.map(metric => ({
|
|
63
|
+
id: `${widget.id}:${metric.id}`, label: metric.label,
|
|
64
|
+
value: typeof metric.value === 'boolean' ? String(metric.value) : metric.value ?? '—',
|
|
65
|
+
...(metric.tone ? { tone: metric.tone } : {}),
|
|
66
|
+
...(metric.href ? { href: metric.href } : {}),
|
|
67
|
+
})) : [],
|
|
68
|
+
) ?? []);
|
|
69
|
+
|
|
70
|
+
$effect(() => {
|
|
71
|
+
void provider; void dashboardId; void requestContext; void filters;
|
|
72
|
+
void captureAuthSession(context.authProvider).cacheKey;
|
|
73
|
+
untrack(() => { snapshot = undefined; void load(); });
|
|
74
|
+
});
|
|
75
|
+
</script>
|
|
76
|
+
|
|
77
|
+
<section class="svadmin-dashboard-view" aria-label={title ?? i18n.t('dashboard.title')}>
|
|
78
|
+
<header class="svadmin-dashboard-view__header">
|
|
79
|
+
<h2>{title ?? i18n.t('dashboard.title')}</h2>
|
|
80
|
+
<Button type="button" variant="outline" size="sm" aria-label={i18n.t('common.refresh')} onclick={() => void load()}>
|
|
81
|
+
<RefreshCw aria-hidden="true" class={loading ? 'svadmin-dashboard-view__spin' : undefined} />
|
|
82
|
+
</Button>
|
|
83
|
+
</header>
|
|
84
|
+
{#if loading}<div role="status"><Loader2 aria-hidden="true" /> {i18n.t('common.loading')}</div>
|
|
85
|
+
{:else if error}<div role="alert">{i18n.t(`dashboard.${error}`)}</div>
|
|
86
|
+
{:else if snapshot}
|
|
87
|
+
{#each snapshot.widgets.filter(widget => widget.kind === 'text') as widget (widget.id)}
|
|
88
|
+
<section aria-labelledby={`dashboard-widget-${widget.id}`}><h3 id={`dashboard-widget-${widget.id}`}>{widget.title}</h3><p>{widget.text}</p></section>
|
|
89
|
+
{/each}
|
|
90
|
+
<MetricStrip items={metricItems} ariaLabel={title ?? i18n.t('dashboard.metrics')} />
|
|
91
|
+
{:else}<div role="status">{i18n.t('dashboard.empty')}</div>{/if}
|
|
92
|
+
</section>
|
|
93
|
+
|
|
94
|
+
<style>
|
|
95
|
+
.svadmin-dashboard-view { display: grid; gap: 1rem; min-width: 0; }
|
|
96
|
+
.svadmin-dashboard-view__header { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; }
|
|
97
|
+
:global(.svadmin-dashboard-view__spin) { animation: svadmin-dashboard-spin 1s linear infinite; }
|
|
98
|
+
@keyframes svadmin-dashboard-spin { to { transform: rotate(360deg); } }
|
|
99
|
+
</style>
|