@svadmin/create 0.33.0 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +26 -8
- package/guidance/AGENTS.md +23 -0
- package/guidance/DESIGN.md +39 -6
- package/package.json +1 -1
- package/scaffold-manifest.json +13 -2
- package/template/patches/@refinedev%2Fcore@5.0.12.patch +30 -0
- package/template/patches/bits-ui@2.19.0.patch +910 -0
- package/template/src/App.svelte +0 -1
- package/template/src/pages/Dashboard.svelte +16 -66
- package/template/src/resource-contracts.ts +20 -1
package/template/src/App.svelte
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { useList } from '@svadmin/core';
|
|
3
3
|
import { posts, users, comments } from '../resource-contracts';
|
|
4
|
-
import { Button, DataState, MetricBlock } from '@svadmin/ui';
|
|
4
|
+
import { AutoTable, Button, DashboardPage, DataState, MetricBlock, PageSection } from '@svadmin/ui';
|
|
5
5
|
|
|
6
6
|
const postsQuery = useList({ resource: posts, pagination: { current: 1, pageSize: 1 } });
|
|
7
7
|
const usersQuery = useList({ resource: users, pagination: { current: 1, pageSize: 1 } });
|
|
@@ -15,78 +15,28 @@
|
|
|
15
15
|
|
|
16
16
|
const hasMetricError = $derived(stats.some(stat => stat.query.isError));
|
|
17
17
|
|
|
18
|
-
const recentPosts = useList({ resource: posts, pagination: { current: 1, pageSize: 5 } });
|
|
19
|
-
const recentUsers = useList({ resource: users, pagination: { current: 1, pageSize: 5 } });
|
|
20
18
|
</script>
|
|
21
19
|
|
|
22
|
-
<
|
|
23
|
-
<h1 class="text-xl font-semibold text-foreground">Dashboard</h1>
|
|
24
|
-
|
|
20
|
+
<DashboardPage title="Dashboard">
|
|
25
21
|
{#if hasMetricError}
|
|
26
22
|
<DataState state="error" title="Some totals are unavailable" retry={() => {
|
|
27
23
|
for (const stat of stats) if (stat.query.isError && !stat.query.isFetching) void stat.query.refetch();
|
|
28
24
|
}} />
|
|
29
25
|
{/if}
|
|
30
|
-
|
|
26
|
+
{#snippet metrics()}
|
|
31
27
|
{#each stats as stat (stat.label)}
|
|
32
28
|
<MetricBlock label={stat.label} value={stat.query.isError ? '—' : stat.query.data?.total ?? '—'} loading={stat.query.isLoading} />
|
|
33
29
|
{/each}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
<
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
</
|
|
43
|
-
{
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
{:else}
|
|
48
|
-
<div class="divide-y">
|
|
49
|
-
{#each recentPosts.data?.data ?? [] as post, _i (_i)}
|
|
50
|
-
<div class="flex items-center justify-between py-3">
|
|
51
|
-
<div class="min-w-0">
|
|
52
|
-
<p class="break-words text-sm font-medium text-foreground">{post.title}</p>
|
|
53
|
-
<p class="text-xs text-muted-foreground">User #{post.userId}</p>
|
|
54
|
-
</div>
|
|
55
|
-
</div>
|
|
56
|
-
{:else}
|
|
57
|
-
<DataState state="empty">
|
|
58
|
-
{#snippet action()}<Button href="#/posts/create">Create post</Button>{/snippet}
|
|
59
|
-
</DataState>
|
|
60
|
-
{/each}
|
|
61
|
-
</div>
|
|
62
|
-
{/if}
|
|
63
|
-
</section>
|
|
64
|
-
|
|
65
|
-
<section class="min-w-0">
|
|
66
|
-
<div class="flex items-center justify-between border-b py-3">
|
|
67
|
-
<h2 class="font-semibold text-foreground">Users</h2>
|
|
68
|
-
<a href="#/users" class="text-sm text-primary hover:underline">View all</a>
|
|
69
|
-
</div>
|
|
70
|
-
{#if recentUsers.isLoading}
|
|
71
|
-
<DataState state="loading" />
|
|
72
|
-
{:else if recentUsers.isError}
|
|
73
|
-
<DataState state="error" retry={() => { if (!recentUsers.isFetching) void recentUsers.refetch(); }} />
|
|
74
|
-
{:else}
|
|
75
|
-
<div class="divide-y">
|
|
76
|
-
{#each recentUsers.data?.data ?? [] as user, _i (_i)}
|
|
77
|
-
<div class="flex items-center justify-between py-3">
|
|
78
|
-
<div class="min-w-0">
|
|
79
|
-
<p class="break-words text-sm font-medium text-foreground">{user.name}</p>
|
|
80
|
-
<p class="break-words text-xs text-muted-foreground">{user.email}</p>
|
|
81
|
-
</div>
|
|
82
|
-
</div>
|
|
83
|
-
{:else}
|
|
84
|
-
<DataState state="empty">
|
|
85
|
-
{#snippet action()}<Button href="#/users">View users</Button>{/snippet}
|
|
86
|
-
</DataState>
|
|
87
|
-
{/each}
|
|
88
|
-
</div>
|
|
89
|
-
{/if}
|
|
90
|
-
</section>
|
|
91
|
-
</div>
|
|
92
|
-
</div>
|
|
30
|
+
{/snippet}
|
|
31
|
+
|
|
32
|
+
<PageSection title="Posts">
|
|
33
|
+
{#snippet actions()}<Button variant="ghost" href="#/posts">View all</Button>{/snippet}
|
|
34
|
+
<AutoTable resourceName="posts" showHeader={false} selectable={false} syncWithLocation={false} />
|
|
35
|
+
</PageSection>
|
|
36
|
+
{#snippet secondary()}
|
|
37
|
+
<PageSection title="Users">
|
|
38
|
+
{#snippet actions()}<Button variant="ghost" href="#/users">View all</Button>{/snippet}
|
|
39
|
+
<AutoTable resourceName="users" showHeader={false} selectable={false} syncWithLocation={false} />
|
|
40
|
+
</PageSection>
|
|
41
|
+
{/snippet}
|
|
42
|
+
</DashboardPage>
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { Type } from '@sinclair/typebox';
|
|
2
|
-
import { defineResource } from '@svadmin/core';
|
|
2
|
+
import { defineResource } from '@svadmin/core/resource-contract';
|
|
3
3
|
|
|
4
|
+
const postInput = Type.Object({
|
|
5
|
+
userId: Type.Number(), title: Type.String({ minLength: 1 }), body: Type.String(),
|
|
6
|
+
});
|
|
4
7
|
export const posts = defineResource('posts', {
|
|
5
8
|
record: Type.Object({
|
|
6
9
|
id: Type.Number(), userId: Type.Number(), title: Type.String(), body: Type.String(),
|
|
7
10
|
}),
|
|
11
|
+
create: postInput,
|
|
12
|
+
update: Type.Partial(postInput),
|
|
8
13
|
});
|
|
9
14
|
export const users = defineResource('users', {
|
|
10
15
|
record: Type.Object({
|
|
@@ -16,14 +21,28 @@ export const users = defineResource('users', {
|
|
|
16
21
|
}),
|
|
17
22
|
company: Type.Object({ name: Type.String(), catchPhrase: Type.String(), bs: Type.String() }),
|
|
18
23
|
}),
|
|
24
|
+
update: Type.Partial(Type.Object({
|
|
25
|
+
name: Type.String(), username: Type.String(), email: Type.String(),
|
|
26
|
+
phone: Type.String(), website: Type.String(),
|
|
27
|
+
})),
|
|
28
|
+
});
|
|
29
|
+
const commentInput = Type.Object({
|
|
30
|
+
postId: Type.Number(), name: Type.String({ minLength: 1 }), email: Type.String(), body: Type.String(),
|
|
19
31
|
});
|
|
20
32
|
export const comments = defineResource('comments', {
|
|
21
33
|
record: Type.Object({
|
|
22
34
|
id: Type.Number(), postId: Type.Number(), name: Type.String(), email: Type.String(), body: Type.String(),
|
|
23
35
|
}),
|
|
36
|
+
create: commentInput,
|
|
37
|
+
update: Type.Partial(commentInput),
|
|
38
|
+
});
|
|
39
|
+
const todoInput = Type.Object({
|
|
40
|
+
userId: Type.Number(), title: Type.String({ minLength: 1 }), completed: Type.Boolean(),
|
|
24
41
|
});
|
|
25
42
|
export const todos = defineResource('todos', {
|
|
26
43
|
record: Type.Object({
|
|
27
44
|
id: Type.Number(), userId: Type.Number(), title: Type.String(), completed: Type.Boolean(),
|
|
28
45
|
}),
|
|
46
|
+
create: todoInput,
|
|
47
|
+
update: Type.Partial(todoInput),
|
|
29
48
|
});
|