@svadmin/ui 0.85.0 → 0.88.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 +31 -0
- package/dist/app-shell.css +216 -0
- package/dist/app.css +659 -165
- package/dist/app.theme.css +659 -165
- package/dist/component-registry.d.ts +7292 -529
- package/dist/component-registry.js +32 -3
- package/dist/components/AppShellPage.svelte +172 -0
- package/dist/components/AppShellPage.svelte.d.ts +30 -0
- package/dist/components/ApprovalActionCard.svelte +54 -31
- package/dist/components/ApprovalCenter.svelte +73 -31
- package/dist/components/AuthPageShell.svelte +13 -2
- package/dist/components/AutoForm.svelte +7 -12
- package/dist/components/AutoForm.svelte.d.ts +0 -1
- package/dist/components/AutoTable.svelte +15 -3
- package/dist/components/AutoTable.svelte.d.ts +2 -0
- package/dist/components/ConfigurationPage.svelte +0 -3
- package/dist/components/ConfigurationPage.svelte.d.ts +0 -1
- package/dist/components/CreatePage.svelte +22 -19
- package/dist/components/CreatePage.svelte.d.ts +4 -1
- package/dist/components/EditPage.svelte +23 -20
- package/dist/components/EditPage.svelte.d.ts +4 -1
- package/dist/components/FieldRenderer.svelte +2 -10
- package/dist/components/FieldRenderer.svelte.d.ts +0 -1
- package/dist/components/ListPage.svelte +21 -18
- package/dist/components/ListPage.svelte.d.ts +2 -1
- package/dist/components/LoginPage.svelte +27 -11
- package/dist/components/LoginPage.svelte.d.ts +4 -0
- package/dist/components/NotificationsSettings.svelte +16 -8
- package/dist/components/PageHeader.svelte +1 -3
- package/dist/components/PageHeader.svelte.d.ts +0 -1
- package/dist/components/PagePattern.svelte +36 -13
- package/dist/components/PagePattern.svelte.d.ts +7 -0
- package/dist/components/PasswordInput.svelte +36 -17
- package/dist/components/PasswordInput.svelte.d.ts +3 -0
- package/dist/components/QuickEditDrawer.svelte +0 -1
- package/dist/components/ShowPage.svelte +24 -29
- package/dist/components/ShowPage.svelte.d.ts +0 -1
- package/dist/components/SvarAutoTable.svelte +2 -1
- package/dist/components/SvarDataGrid.svelte +8 -4
- package/dist/components/SvarDataGrid.svelte.d.ts +3 -0
- package/dist/components/SvarResourceTable.svelte +0 -1
- package/dist/components/SvarResourceTable.svelte.d.ts +0 -1
- package/dist/components/SvarResourceTableView.svelte +2 -2
- package/dist/components/account/MemberDirectory.svelte +17 -4
- package/dist/components/account/SecurityLogPage.svelte +8 -1
- package/dist/components/auto-table-grid.d.ts +1 -0
- package/dist/components/content/AuditTimeline.svelte +32 -4
- package/dist/components/content/AuditTimeline.svelte.d.ts +9 -0
- package/dist/components/content/ContentPageHeader.svelte +2 -1
- package/dist/components/content/ContentPageHeader.svelte.d.ts +1 -0
- package/dist/components/content/ContentPageShell.svelte +1 -1
- package/dist/components/content/ContentPageShell.svelte.d.ts +1 -0
- package/dist/components/content/DataPanel.svelte +58 -0
- package/dist/components/content/DataPanel.svelte.d.ts +16 -0
- package/dist/components/content/DataState.svelte +5 -4
- package/dist/components/content/DataState.svelte.d.ts +1 -1
- package/dist/components/content/DescriptionList.svelte +5 -8
- package/dist/components/content/DescriptionList.svelte.d.ts +0 -1
- package/dist/components/content/FilterToolbar.svelte +9 -6
- package/dist/components/content/FilterToolbar.svelte.d.ts +0 -1
- package/dist/components/content/NetworkTable.svelte +3 -3
- package/dist/components/content/NetworkTable.svelte.d.ts +0 -1
- package/dist/components/content/PageSection.svelte +1 -1
- package/dist/components/content/StatusTabs.svelte +2 -7
- package/dist/components/content/StatusTabs.svelte.d.ts +0 -1
- package/dist/components/content/WorkspaceLayout.svelte +3 -1
- package/dist/components/content/WorkspaceLayout.svelte.d.ts +1 -0
- package/dist/components/content/index.d.ts +1 -0
- package/dist/components/content/index.js +1 -0
- package/dist/components/network/TeamCrewTablePage.svelte +8 -1
- package/dist/components/network/UserCardsNFTPage.svelte +3 -1
- package/dist/components/profile/ProjectsGrid.svelte +1 -1
- package/dist/components/profile/TeamsShowcase.svelte +1 -1
- package/dist/components/ui/table/index.d.ts +0 -1
- package/dist/components/ui/table/table.svelte +1 -0
- package/dist/components/ui/table/table.svelte.d.ts +1 -0
- package/dist/components.css +1 -11
- package/dist/data-panel.css +88 -0
- package/dist/design-contract.d.ts +11 -0
- package/dist/design-contract.js +8 -0
- package/dist/design-system.d.ts +3 -2
- package/dist/design-system.js +3 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +2 -0
- package/dist/page-chrome.css +42 -64
- package/dist/recipes.d.ts +12 -66
- package/dist/recipes.js +52 -50
- package/dist/styles/recipes.css +15 -48
- package/package.json +4 -4
|
@@ -12,6 +12,36 @@ function variant(recipe, name) {
|
|
|
12
12
|
};
|
|
13
13
|
}
|
|
14
14
|
const registryEntries = [
|
|
15
|
+
{
|
|
16
|
+
id: "page-pattern",
|
|
17
|
+
component: "@svadmin/ui/components/PagePattern.svelte",
|
|
18
|
+
recipe: "contentPageRecipe",
|
|
19
|
+
props: {
|
|
20
|
+
kind: { kind: "enum", values: ["list", "form", "detail", "workspace", "dashboard"], required: true },
|
|
21
|
+
title: { kind: "string", required: true },
|
|
22
|
+
description: { kind: "string" },
|
|
23
|
+
eyebrow: { kind: "string" },
|
|
24
|
+
pageId: { kind: "string" },
|
|
25
|
+
width: variant("contentPageRecipe", "width"),
|
|
26
|
+
density: { kind: "enum", values: ["comfortable", "compact"], default: "comfortable", description: "Deprecated compatibility prop; use the shared baseline." },
|
|
27
|
+
state: { kind: "enum", values: ["ready", "loading", "empty", "filtered-empty", "error", "forbidden"], default: "ready" },
|
|
28
|
+
stateTitle: { kind: "string" },
|
|
29
|
+
stateDescription: { kind: "string" },
|
|
30
|
+
retry: { kind: "callback", description: "Use an error-state retry callback." },
|
|
31
|
+
class: { kind: "string" },
|
|
32
|
+
},
|
|
33
|
+
slots: {
|
|
34
|
+
children: { kind: "snippet", required: true },
|
|
35
|
+
actions: { kind: "snippet" },
|
|
36
|
+
toolbar: { kind: "snippet" },
|
|
37
|
+
metrics: { kind: "snippet" },
|
|
38
|
+
secondary: { kind: "snippet" },
|
|
39
|
+
footer: { kind: "snippet" },
|
|
40
|
+
stateAction: { kind: "snippet" },
|
|
41
|
+
},
|
|
42
|
+
tokens: semanticTokens,
|
|
43
|
+
states: ["ready", "loading", "empty", "filtered-empty", "error", "forbidden"],
|
|
44
|
+
},
|
|
15
45
|
{
|
|
16
46
|
id: "content-page-shell",
|
|
17
47
|
component: "@svadmin/ui/components/content/ContentPageShell.svelte",
|
|
@@ -22,7 +52,7 @@ const registryEntries = [
|
|
|
22
52
|
description: { kind: "string" },
|
|
23
53
|
pageId: { kind: "string" },
|
|
24
54
|
width: variant("contentPageRecipe", "width"),
|
|
25
|
-
density:
|
|
55
|
+
density: { kind: "enum", values: ["comfortable", "compact"], default: "comfortable", description: "Deprecated compatibility prop; use the shared baseline." },
|
|
26
56
|
class: { kind: "string" },
|
|
27
57
|
},
|
|
28
58
|
slots: { children: { kind: "snippet", required: true }, actions: { kind: "snippet" } },
|
|
@@ -38,7 +68,6 @@ const registryEntries = [
|
|
|
38
68
|
eyebrow: { kind: "string" },
|
|
39
69
|
description: { kind: "string" },
|
|
40
70
|
breadcrumbs: { kind: "string[]" },
|
|
41
|
-
density: variant("contentHeaderRecipe", "density"),
|
|
42
71
|
class: { kind: "string" },
|
|
43
72
|
},
|
|
44
73
|
slots: { actions: { kind: "snippet" } },
|
|
@@ -208,7 +237,7 @@ export function createComponentRegistryManifest() {
|
|
|
208
237
|
return {
|
|
209
238
|
version: "svadmin/component-registry-v1",
|
|
210
239
|
purpose: "authoring-only",
|
|
211
|
-
coverage: "
|
|
240
|
+
coverage: "page-pattern-and-semantic-ui-components",
|
|
212
241
|
runtimeCatalog: "@svadmin/surface: createSurfaceCatalogManifest",
|
|
213
242
|
designContract: surfaceDesignContract,
|
|
214
243
|
recipes: Object.fromEntries(Object.keys(allRecipes)
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import { Menu, X } from '@lucide/svelte';
|
|
4
|
+
import { useTranslation } from '@svadmin/core/i18n';
|
|
5
|
+
import { cn } from '../utils.js';
|
|
6
|
+
|
|
7
|
+
type Width = 'narrow' | 'default' | 'wide' | 'full';
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
/** Page content; typically a `PagePattern` or `ContentPageShell`. */
|
|
11
|
+
children: Snippet;
|
|
12
|
+
/** Navigation region. The host owns links and routing; the shell owns layout. */
|
|
13
|
+
sidebar?: Snippet;
|
|
14
|
+
/** Leading topbar region, rendered before the mobile navigation trigger. */
|
|
15
|
+
topbarStart?: Snippet;
|
|
16
|
+
/** Topbar region between the leading and trailing slots (search, breadcrumbs…). */
|
|
17
|
+
topbar?: Snippet;
|
|
18
|
+
/** Trailing topbar actions (account menu, notifications…). */
|
|
19
|
+
topbarEnd?: Snippet;
|
|
20
|
+
/** Optional secondary column for detail/workspace pages. */
|
|
21
|
+
aside?: Snippet;
|
|
22
|
+
footer?: Snippet;
|
|
23
|
+
width?: Width;
|
|
24
|
+
/** Collapses the navigation rail to icons. Host-controlled, presentation only. */
|
|
25
|
+
collapsed?: boolean;
|
|
26
|
+
/** Open state of the below-breakpoint navigation drawer. */
|
|
27
|
+
mobileOpen?: boolean;
|
|
28
|
+
/** Accessible name for the primary landmark. */
|
|
29
|
+
label?: string;
|
|
30
|
+
skipLabel?: string;
|
|
31
|
+
contentId?: string;
|
|
32
|
+
class?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let {
|
|
36
|
+
children,
|
|
37
|
+
sidebar,
|
|
38
|
+
topbarStart,
|
|
39
|
+
topbar,
|
|
40
|
+
topbarEnd,
|
|
41
|
+
aside,
|
|
42
|
+
footer,
|
|
43
|
+
width = 'default',
|
|
44
|
+
collapsed = false,
|
|
45
|
+
mobileOpen = $bindable(false),
|
|
46
|
+
label,
|
|
47
|
+
skipLabel,
|
|
48
|
+
contentId,
|
|
49
|
+
class: className = '',
|
|
50
|
+
}: Props = $props();
|
|
51
|
+
|
|
52
|
+
const generatedId = $props.id();
|
|
53
|
+
const i18n = useTranslation();
|
|
54
|
+
const mainId = $derived(contentId ?? generatedId);
|
|
55
|
+
const sidebarId = $derived(`${generatedId}-sidebar`);
|
|
56
|
+
// 未提供可读名称时退回通用文案,避免出现没有可访问名的 <main>。
|
|
57
|
+
const resolvedLabel = $derived(label ?? i18n.t('common.mainNavigation'));
|
|
58
|
+
const resolvedSkipLabel = $derived(skipLabel ?? i18n.t('common.skipToMainContent'));
|
|
59
|
+
|
|
60
|
+
function closeMobileNav() {
|
|
61
|
+
mobileOpen = false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function onKeydown(event: KeyboardEvent) {
|
|
65
|
+
if (event.key === 'Escape' && mobileOpen) closeMobileNav();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 宿主常用 hash 路由,不能让跳转链接把 #main 当成一次路由跳转。
|
|
69
|
+
function focusMain(event: MouseEvent) {
|
|
70
|
+
const target = document.getElementById(mainId);
|
|
71
|
+
if (!target) return;
|
|
72
|
+
event.preventDefault();
|
|
73
|
+
target.focus();
|
|
74
|
+
target.scrollIntoView?.({ block: 'start' });
|
|
75
|
+
}
|
|
76
|
+
</script>
|
|
77
|
+
|
|
78
|
+
<svelte:window onkeydown={onKeydown} />
|
|
79
|
+
|
|
80
|
+
<a class="svadmin-app-shell__skip" href="#{mainId}" onclick={focusMain}>{resolvedSkipLabel}</a>
|
|
81
|
+
|
|
82
|
+
<div
|
|
83
|
+
class={cn('svadmin-app-shell', className)}
|
|
84
|
+
data-sidebar={sidebar ? 'true' : undefined}
|
|
85
|
+
data-aside={aside ? 'true' : undefined}
|
|
86
|
+
data-collapsed={collapsed ? 'true' : undefined}
|
|
87
|
+
data-mobile-open={mobileOpen ? 'true' : undefined}
|
|
88
|
+
data-width={width}
|
|
89
|
+
>
|
|
90
|
+
<header class="svadmin-app-shell__topbar" data-slot="app-shell-topbar">
|
|
91
|
+
{#if sidebar}
|
|
92
|
+
<button
|
|
93
|
+
type="button"
|
|
94
|
+
class="svadmin-app-shell__mobile-toggle"
|
|
95
|
+
data-slot="app-shell-mobile-toggle"
|
|
96
|
+
aria-controls={sidebarId}
|
|
97
|
+
aria-expanded={mobileOpen ? 'true' : 'false'}
|
|
98
|
+
aria-label={mobileOpen ? i18n.t('common.close') : i18n.t('common.menu')}
|
|
99
|
+
onclick={() => (mobileOpen = !mobileOpen)}
|
|
100
|
+
>
|
|
101
|
+
{#if mobileOpen}
|
|
102
|
+
<X aria-hidden="true" size={18} />
|
|
103
|
+
{:else}
|
|
104
|
+
<Menu aria-hidden="true" size={18} />
|
|
105
|
+
{/if}
|
|
106
|
+
</button>
|
|
107
|
+
{/if}
|
|
108
|
+
{#if topbarStart}
|
|
109
|
+
<div class="svadmin-app-shell__topbar-start">{@render topbarStart()}</div>
|
|
110
|
+
{/if}
|
|
111
|
+
<div class="svadmin-app-shell__topbar-main">{@render topbar?.()}</div>
|
|
112
|
+
{#if topbarEnd}
|
|
113
|
+
<div class="svadmin-app-shell__topbar-end">{@render topbarEnd()}</div>
|
|
114
|
+
{/if}
|
|
115
|
+
</header>
|
|
116
|
+
|
|
117
|
+
{#if sidebar}
|
|
118
|
+
<nav id={sidebarId} class="svadmin-app-shell__sidebar" aria-label={resolvedLabel} data-slot="app-shell-sidebar">
|
|
119
|
+
<div class="svadmin-app-shell__sidebar-inner">{@render sidebar()}</div>
|
|
120
|
+
</nav>
|
|
121
|
+
<button
|
|
122
|
+
type="button"
|
|
123
|
+
class="svadmin-app-shell__scrim"
|
|
124
|
+
tabindex="-1"
|
|
125
|
+
aria-hidden="true"
|
|
126
|
+
data-slot="app-shell-scrim"
|
|
127
|
+
onclick={closeMobileNav}
|
|
128
|
+
></button>
|
|
129
|
+
{/if}
|
|
130
|
+
|
|
131
|
+
<main id={mainId} class="svadmin-app-shell__main" tabindex="-1">
|
|
132
|
+
<div class="svadmin-app-shell__body" data-slot="app-shell-body">
|
|
133
|
+
<div class="svadmin-app-shell__content" data-slot="app-shell-content">
|
|
134
|
+
{@render children()}
|
|
135
|
+
</div>
|
|
136
|
+
{#if aside}
|
|
137
|
+
<aside class="svadmin-app-shell__aside" data-slot="app-shell-aside">{@render aside()}</aside>
|
|
138
|
+
{/if}
|
|
139
|
+
</div>
|
|
140
|
+
</main>
|
|
141
|
+
|
|
142
|
+
{#if footer}
|
|
143
|
+
<footer class="svadmin-app-shell__footer" data-slot="app-shell-footer">{@render footer()}</footer>
|
|
144
|
+
{/if}
|
|
145
|
+
</div>
|
|
146
|
+
|
|
147
|
+
<style>
|
|
148
|
+
/* Layout-only overrides. Sizes, colors and radii stay in app-shell.css so a
|
|
149
|
+
* host override of the token layer reaches this component too. */
|
|
150
|
+
.svadmin-app-shell__mobile-toggle {
|
|
151
|
+
align-items: center;
|
|
152
|
+
justify-content: center;
|
|
153
|
+
flex: 0 0 auto;
|
|
154
|
+
height: var(--svadmin-control-h);
|
|
155
|
+
width: var(--svadmin-control-h);
|
|
156
|
+
border: 1px solid var(--border);
|
|
157
|
+
border-radius: var(--radius-md);
|
|
158
|
+
background: var(--background);
|
|
159
|
+
color: var(--foreground);
|
|
160
|
+
cursor: pointer;
|
|
161
|
+
}
|
|
162
|
+
.svadmin-app-shell__mobile-toggle:hover {
|
|
163
|
+
background: var(--muted);
|
|
164
|
+
}
|
|
165
|
+
.svadmin-app-shell__mobile-toggle:focus-visible {
|
|
166
|
+
outline: var(--svadmin-focus-width) solid var(--ring);
|
|
167
|
+
outline-offset: var(--svadmin-focus-offset);
|
|
168
|
+
}
|
|
169
|
+
.svadmin-app-shell__main:focus-visible {
|
|
170
|
+
outline: none;
|
|
171
|
+
}
|
|
172
|
+
</style>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
type Width = 'narrow' | 'default' | 'wide' | 'full';
|
|
3
|
+
interface Props {
|
|
4
|
+
/** Page content; typically a `PagePattern` or `ContentPageShell`. */
|
|
5
|
+
children: Snippet;
|
|
6
|
+
/** Navigation region. The host owns links and routing; the shell owns layout. */
|
|
7
|
+
sidebar?: Snippet;
|
|
8
|
+
/** Leading topbar region, rendered before the mobile navigation trigger. */
|
|
9
|
+
topbarStart?: Snippet;
|
|
10
|
+
/** Topbar region between the leading and trailing slots (search, breadcrumbs…). */
|
|
11
|
+
topbar?: Snippet;
|
|
12
|
+
/** Trailing topbar actions (account menu, notifications…). */
|
|
13
|
+
topbarEnd?: Snippet;
|
|
14
|
+
/** Optional secondary column for detail/workspace pages. */
|
|
15
|
+
aside?: Snippet;
|
|
16
|
+
footer?: Snippet;
|
|
17
|
+
width?: Width;
|
|
18
|
+
/** Collapses the navigation rail to icons. Host-controlled, presentation only. */
|
|
19
|
+
collapsed?: boolean;
|
|
20
|
+
/** Open state of the below-breakpoint navigation drawer. */
|
|
21
|
+
mobileOpen?: boolean;
|
|
22
|
+
/** Accessible name for the primary landmark. */
|
|
23
|
+
label?: string;
|
|
24
|
+
skipLabel?: string;
|
|
25
|
+
contentId?: string;
|
|
26
|
+
class?: string;
|
|
27
|
+
}
|
|
28
|
+
declare const AppShellPage: import("svelte").Component<Props, {}, "mobileOpen">;
|
|
29
|
+
type AppShellPage = ReturnType<typeof AppShellPage>;
|
|
30
|
+
export default AppShellPage;
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { Button } from './ui/button/index.js';
|
|
5
5
|
import { Badge } from './ui/badge/index.js';
|
|
6
6
|
import * as Dialog from './ui/dialog/index.js';
|
|
7
|
+
import { useTranslation } from '@svadmin/core/i18n';
|
|
7
8
|
import { cn } from '../utils.js';
|
|
8
9
|
|
|
9
10
|
export type ApprovalStatus = 'pending' | 'approved' | 'rejected' | 'recalled';
|
|
@@ -28,7 +29,7 @@
|
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
let {
|
|
31
|
-
title
|
|
32
|
+
title,
|
|
32
33
|
status = 'pending',
|
|
33
34
|
applicant,
|
|
34
35
|
onapprove,
|
|
@@ -39,6 +40,10 @@
|
|
|
39
40
|
children,
|
|
40
41
|
}: Props = $props();
|
|
41
42
|
|
|
43
|
+
const controlId = $props.id();
|
|
44
|
+
const i18n = useTranslation();
|
|
45
|
+
const isZh = $derived(i18n.locale === 'zh-CN');
|
|
46
|
+
const resolvedTitle = $derived(title ?? (isZh ? '审批申请' : 'Approval Request'));
|
|
42
47
|
let approveDialogOpen = $state(false);
|
|
43
48
|
let rejectDialogOpen = $state(false);
|
|
44
49
|
let transferDialogOpen = $state(false);
|
|
@@ -47,26 +52,35 @@
|
|
|
47
52
|
let rejectReason = $state('');
|
|
48
53
|
let transferTarget = $state('');
|
|
49
54
|
let isSubmitting = $state(false);
|
|
55
|
+
let errorMessage = $state('');
|
|
50
56
|
|
|
51
57
|
const statusConfig = $derived.by(() => {
|
|
52
58
|
switch (status) {
|
|
53
59
|
case 'approved':
|
|
54
|
-
return { label: 'Approved', icon: CheckCircle2 };
|
|
60
|
+
return { label: isZh ? '已通过' : 'Approved', icon: CheckCircle2 };
|
|
55
61
|
case 'rejected':
|
|
56
|
-
return { label: 'Rejected', icon: XCircle };
|
|
62
|
+
return { label: isZh ? '已驳回' : 'Rejected', icon: XCircle };
|
|
57
63
|
case 'recalled':
|
|
58
|
-
return { label: 'Recalled', icon: Undo2 };
|
|
64
|
+
return { label: isZh ? '已撤回' : 'Recalled', icon: Undo2 };
|
|
59
65
|
default:
|
|
60
|
-
return { label: 'Pending Approval', icon: Clock };
|
|
66
|
+
return { label: isZh ? '待审批' : 'Pending Approval', icon: Clock };
|
|
61
67
|
}
|
|
62
68
|
});
|
|
63
69
|
|
|
70
|
+
function failureMessage(caught: unknown): string {
|
|
71
|
+
if (caught instanceof Error && caught.message.trim()) return caught.message;
|
|
72
|
+
return isZh ? '操作失败,请检查输入后重试。' : 'Action failed. Check the input and retry.';
|
|
73
|
+
}
|
|
74
|
+
|
|
64
75
|
async function handleApprove() {
|
|
65
76
|
isSubmitting = true;
|
|
77
|
+
errorMessage = '';
|
|
66
78
|
try {
|
|
67
79
|
await onapprove?.(approveComment || undefined);
|
|
68
80
|
approveDialogOpen = false;
|
|
69
81
|
approveComment = '';
|
|
82
|
+
} catch (caught) {
|
|
83
|
+
errorMessage = failureMessage(caught);
|
|
70
84
|
} finally {
|
|
71
85
|
isSubmitting = false;
|
|
72
86
|
}
|
|
@@ -75,10 +89,13 @@
|
|
|
75
89
|
async function handleReject() {
|
|
76
90
|
if (!rejectReason.trim()) return;
|
|
77
91
|
isSubmitting = true;
|
|
92
|
+
errorMessage = '';
|
|
78
93
|
try {
|
|
79
94
|
await onreject?.(rejectReason);
|
|
80
95
|
rejectDialogOpen = false;
|
|
81
96
|
rejectReason = '';
|
|
97
|
+
} catch (caught) {
|
|
98
|
+
errorMessage = failureMessage(caught);
|
|
82
99
|
} finally {
|
|
83
100
|
isSubmitting = false;
|
|
84
101
|
}
|
|
@@ -87,10 +104,13 @@
|
|
|
87
104
|
async function handleTransfer() {
|
|
88
105
|
if (!transferTarget.trim()) return;
|
|
89
106
|
isSubmitting = true;
|
|
107
|
+
errorMessage = '';
|
|
90
108
|
try {
|
|
91
109
|
await ontransfer?.(transferTarget);
|
|
92
110
|
transferDialogOpen = false;
|
|
93
111
|
transferTarget = '';
|
|
112
|
+
} catch (caught) {
|
|
113
|
+
errorMessage = failureMessage(caught);
|
|
94
114
|
} finally {
|
|
95
115
|
isSubmitting = false;
|
|
96
116
|
}
|
|
@@ -101,7 +121,7 @@
|
|
|
101
121
|
<div class="svadmin-approval-card__header">
|
|
102
122
|
<div class="svadmin-approval-card__heading">
|
|
103
123
|
<div class="svadmin-approval-card__title-row">
|
|
104
|
-
<h4 class="svadmin-approval-card__title">{
|
|
124
|
+
<h4 class="svadmin-approval-card__title">{resolvedTitle}</h4>
|
|
105
125
|
<Badge variant="outline" data-status={status}>
|
|
106
126
|
<statusConfig.icon class="svadmin-approval-card__status-icon" />
|
|
107
127
|
{statusConfig.label}
|
|
@@ -109,7 +129,7 @@
|
|
|
109
129
|
</div>
|
|
110
130
|
{#if applicant}
|
|
111
131
|
<p class="svadmin-approval-card__applicant">
|
|
112
|
-
Submitted by <strong>{applicant.name}</strong>
|
|
132
|
+
{isZh ? '提交人:' : 'Submitted by '}<strong>{applicant.name}</strong>
|
|
113
133
|
{#if applicant.department} ({applicant.department}){/if}
|
|
114
134
|
{#if applicant.time} · {applicant.time}{/if}
|
|
115
135
|
</p>
|
|
@@ -124,10 +144,10 @@
|
|
|
124
144
|
size="sm"
|
|
125
145
|
class="svadmin-approval-card__action"
|
|
126
146
|
{disabled}
|
|
127
|
-
onclick={() => { transferDialogOpen = true; }}
|
|
147
|
+
onclick={() => { transferDialogOpen = true; errorMessage = ''; }}
|
|
128
148
|
>
|
|
129
149
|
<ArrowRightLeft class="svadmin-approval-card__action-icon" />
|
|
130
|
-
Transfer
|
|
150
|
+
{isZh ? '转交' : 'Transfer'}
|
|
131
151
|
</Button>
|
|
132
152
|
{/if}
|
|
133
153
|
|
|
@@ -137,10 +157,10 @@
|
|
|
137
157
|
size="sm"
|
|
138
158
|
class="svadmin-approval-card__action svadmin-approval-card__action--reject"
|
|
139
159
|
{disabled}
|
|
140
|
-
onclick={() => { rejectDialogOpen = true; }}
|
|
160
|
+
onclick={() => { rejectDialogOpen = true; errorMessage = ''; }}
|
|
141
161
|
>
|
|
142
162
|
<X class="svadmin-approval-card__action-icon" />
|
|
143
|
-
Reject
|
|
163
|
+
{isZh ? '驳回' : 'Reject'}
|
|
144
164
|
</Button>
|
|
145
165
|
{/if}
|
|
146
166
|
|
|
@@ -149,10 +169,10 @@
|
|
|
149
169
|
size="sm"
|
|
150
170
|
class="svadmin-approval-card__action svadmin-approval-card__action--approve"
|
|
151
171
|
{disabled}
|
|
152
|
-
onclick={() => { approveDialogOpen = true; }}
|
|
172
|
+
onclick={() => { approveDialogOpen = true; errorMessage = ''; }}
|
|
153
173
|
>
|
|
154
174
|
<Check class="svadmin-approval-card__action-icon" />
|
|
155
|
-
Approve
|
|
175
|
+
{isZh ? '通过' : 'Approve'}
|
|
156
176
|
</Button>
|
|
157
177
|
{/if}
|
|
158
178
|
</div>
|
|
@@ -171,21 +191,22 @@
|
|
|
171
191
|
<Dialog.Dialog bind:open={approveDialogOpen}>
|
|
172
192
|
<Dialog.DialogContent class="svadmin-approval-card__dialog">
|
|
173
193
|
<Dialog.DialogHeader>
|
|
174
|
-
<Dialog.DialogTitle>Confirm Approval</Dialog.DialogTitle>
|
|
194
|
+
<Dialog.DialogTitle>{isZh ? '确认通过' : 'Confirm Approval'}</Dialog.DialogTitle>
|
|
175
195
|
</Dialog.DialogHeader>
|
|
176
196
|
<div class="svadmin-approval-card__dialog-form">
|
|
177
|
-
<label class="svadmin-approval-card__dialog-label" for=
|
|
197
|
+
<label class="svadmin-approval-card__dialog-label" for={`${controlId}-approve-comment`}>{isZh ? '审批意见(可选)' : 'Optional Approval Comment'}</label>
|
|
178
198
|
<textarea
|
|
179
|
-
id=
|
|
199
|
+
id={`${controlId}-approve-comment`}
|
|
180
200
|
bind:value={approveComment}
|
|
181
|
-
placeholder=
|
|
201
|
+
placeholder={isZh ? '例如:已核验,准予通过' : 'e.g. Verified and approved'}
|
|
182
202
|
class="svadmin-approval-card__textarea"
|
|
183
203
|
></textarea>
|
|
204
|
+
{#if errorMessage}<p role="alert" class="svadmin-approval-card__error text-destructive">{errorMessage}</p>{/if}
|
|
184
205
|
</div>
|
|
185
206
|
<Dialog.DialogFooter>
|
|
186
|
-
<Button variant="outline" size="sm" onclick={() => { approveDialogOpen = false; }}>Cancel</Button>
|
|
207
|
+
<Button variant="outline" size="sm" onclick={() => { approveDialogOpen = false; errorMessage = ''; }}>{isZh ? '取消' : 'Cancel'}</Button>
|
|
187
208
|
<Button size="sm" class="svadmin-approval-card__confirm svadmin-approval-card__confirm--approve" disabled={isSubmitting} onclick={handleApprove}>
|
|
188
|
-
Confirm Approval
|
|
209
|
+
{isSubmitting ? (isZh ? '处理中…' : 'Processing…') : (isZh ? '确认通过' : 'Confirm Approval')}
|
|
189
210
|
</Button>
|
|
190
211
|
</Dialog.DialogFooter>
|
|
191
212
|
</Dialog.DialogContent>
|
|
@@ -197,22 +218,23 @@
|
|
|
197
218
|
<Dialog.Dialog bind:open={rejectDialogOpen}>
|
|
198
219
|
<Dialog.DialogContent class="svadmin-approval-card__dialog">
|
|
199
220
|
<Dialog.DialogHeader>
|
|
200
|
-
<Dialog.DialogTitle class="svadmin-approval-card__reject-title">Reject Request</Dialog.DialogTitle>
|
|
221
|
+
<Dialog.DialogTitle class="svadmin-approval-card__reject-title">{isZh ? '驳回申请' : 'Reject Request'}</Dialog.DialogTitle>
|
|
201
222
|
</Dialog.DialogHeader>
|
|
202
223
|
<div class="svadmin-approval-card__dialog-form">
|
|
203
|
-
<label class="svadmin-approval-card__dialog-label" for=
|
|
224
|
+
<label class="svadmin-approval-card__dialog-label" for={`${controlId}-reject-reason`}>{isZh ? '驳回原因(必填)' : 'Rejection Reason (Required)'}</label>
|
|
204
225
|
<textarea
|
|
205
|
-
id=
|
|
226
|
+
id={`${controlId}-reject-reason`}
|
|
206
227
|
bind:value={rejectReason}
|
|
207
|
-
placeholder=
|
|
228
|
+
placeholder={isZh ? '请说明驳回原因…' : 'Please explain why this request is being rejected...'}
|
|
208
229
|
class="svadmin-approval-card__textarea svadmin-approval-card__textarea--reject"
|
|
209
230
|
required
|
|
210
231
|
></textarea>
|
|
232
|
+
{#if errorMessage}<p role="alert" class="svadmin-approval-card__error text-destructive">{errorMessage}</p>{/if}
|
|
211
233
|
</div>
|
|
212
234
|
<Dialog.DialogFooter>
|
|
213
|
-
<Button variant="outline" size="sm" onclick={() => { rejectDialogOpen = false; }}>Cancel</Button>
|
|
235
|
+
<Button variant="outline" size="sm" onclick={() => { rejectDialogOpen = false; errorMessage = ''; }}>{isZh ? '取消' : 'Cancel'}</Button>
|
|
214
236
|
<Button variant="destructive" size="sm" disabled={isSubmitting || !rejectReason.trim()} onclick={handleReject}>
|
|
215
|
-
Confirm Rejection
|
|
237
|
+
{isSubmitting ? (isZh ? '处理中…' : 'Processing…') : (isZh ? '确认驳回' : 'Confirm Rejection')}
|
|
216
238
|
</Button>
|
|
217
239
|
</Dialog.DialogFooter>
|
|
218
240
|
</Dialog.DialogContent>
|
|
@@ -224,23 +246,24 @@
|
|
|
224
246
|
<Dialog.Dialog bind:open={transferDialogOpen}>
|
|
225
247
|
<Dialog.DialogContent class="svadmin-approval-card__dialog">
|
|
226
248
|
<Dialog.DialogHeader>
|
|
227
|
-
<Dialog.DialogTitle>Transfer Approval</Dialog.DialogTitle>
|
|
249
|
+
<Dialog.DialogTitle>{isZh ? '转交审批' : 'Transfer Approval'}</Dialog.DialogTitle>
|
|
228
250
|
</Dialog.DialogHeader>
|
|
229
251
|
<div class="svadmin-approval-card__dialog-form">
|
|
230
|
-
<label class="svadmin-approval-card__dialog-label" for=
|
|
252
|
+
<label class="svadmin-approval-card__dialog-label" for={`${controlId}-transfer-target`}>{isZh ? '目标审批人邮箱或用户名' : 'Target Approver Email / Username'}</label>
|
|
231
253
|
<input
|
|
232
|
-
id=
|
|
254
|
+
id={`${controlId}-transfer-target`}
|
|
233
255
|
type="text"
|
|
234
256
|
bind:value={transferTarget}
|
|
235
|
-
placeholder=
|
|
257
|
+
placeholder={isZh ? '例如:manager@example.com' : 'e.g. manager@example.com'}
|
|
236
258
|
class="svadmin-approval-card__input"
|
|
237
259
|
required
|
|
238
260
|
/>
|
|
261
|
+
{#if errorMessage}<p role="alert" class="svadmin-approval-card__error text-destructive">{errorMessage}</p>{/if}
|
|
239
262
|
</div>
|
|
240
263
|
<Dialog.DialogFooter>
|
|
241
|
-
<Button variant="outline" size="sm" onclick={() => { transferDialogOpen = false; }}>Cancel</Button>
|
|
264
|
+
<Button variant="outline" size="sm" onclick={() => { transferDialogOpen = false; errorMessage = ''; }}>{isZh ? '取消' : 'Cancel'}</Button>
|
|
242
265
|
<Button size="sm" disabled={isSubmitting || !transferTarget.trim()} onclick={handleTransfer}>
|
|
243
|
-
Transfer
|
|
266
|
+
{isSubmitting ? (isZh ? '处理中…' : 'Processing…') : (isZh ? '转交' : 'Transfer')}
|
|
244
267
|
</Button>
|
|
245
268
|
</Dialog.DialogFooter>
|
|
246
269
|
</Dialog.DialogContent>
|