@frigear-nu/admin 0.0.2 → 0.0.3
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/module.json +1 -1
- package/dist/module.mjs +1 -1
- package/dist/runtime/app/components/AdminListCard.d.vue.ts +24 -0
- package/dist/runtime/app/components/AdminListCard.vue +71 -0
- package/dist/runtime/app/components/AdminListCard.vue.d.ts +24 -0
- package/dist/runtime/app/components/AdminPage.vue +1 -1
- package/dist/runtime/app/components/AdminPageHeader.d.vue.ts +7 -7
- package/dist/runtime/app/components/AdminPageHeader.vue +22 -32
- package/dist/runtime/app/components/AdminPageHeader.vue.d.ts +7 -7
- package/dist/runtime/app/components/AdminSection.vue +3 -3
- package/dist/runtime/app/components/AdminStatCard.d.vue.ts +4 -2
- package/dist/runtime/app/components/AdminStatCard.vue +41 -22
- package/dist/runtime/app/components/AdminStatCard.vue.d.ts +4 -2
- package/dist/runtime/app/components/AdminTable.vue +1 -1
- package/dist/runtime/app/layouts/admin.vue +84 -61
- package/dist/runtime/app/pages/dashboard.vue +49 -23
- package/dist/runtime/utils/index.d.ts +11 -2
- package/dist/runtime/utils/index.js +8 -1
- package/package.json +4 -4
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -26,7 +26,7 @@ const module$1 = defineNuxtModule({
|
|
|
26
26
|
const dashboardPath = settings.dashboard === false ? void 0 : typeof settings.dashboard === "string" ? settings.dashboard : base;
|
|
27
27
|
nuxt.options.runtimeConfig.public.admin = { base, dashboard: dashboardPath };
|
|
28
28
|
addLayout({ src: resolver.resolve("./runtime/app/layouts/admin.vue"), filename: "admin.vue" }, "admin");
|
|
29
|
-
for (const name of ["AdminPage", "AdminPageHeader", "AdminSection", "AdminToolbar", "AdminEmpty", "AdminTable", "AdminStatCard", "AdminKeyValue"]) {
|
|
29
|
+
for (const name of ["AdminPage", "AdminPageHeader", "AdminSection", "AdminToolbar", "AdminEmpty", "AdminTable", "AdminStatCard", "AdminKeyValue", "AdminListCard"]) {
|
|
30
30
|
addComponent({ name, filePath: resolver.resolve(`./runtime/app/components/${name}.vue`) });
|
|
31
31
|
}
|
|
32
32
|
const prefixes = ["", ...(nuxt.options.i18n?.locales ?? []).map((locale) => typeof locale === "string" ? locale : locale.code).map((code) => `/${code}`)];
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
title: string;
|
|
3
|
+
description?: string;
|
|
4
|
+
loading?: boolean;
|
|
5
|
+
isEmpty?: boolean;
|
|
6
|
+
empty?: string;
|
|
7
|
+
to?: string;
|
|
8
|
+
toLabel?: string;
|
|
9
|
+
};
|
|
10
|
+
declare var __VLS_1: {}, __VLS_18: {};
|
|
11
|
+
type __VLS_Slots = {} & {
|
|
12
|
+
actions?: (props: typeof __VLS_1) => any;
|
|
13
|
+
} & {
|
|
14
|
+
default?: (props: typeof __VLS_18) => any;
|
|
15
|
+
};
|
|
16
|
+
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
17
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
18
|
+
declare const _default: typeof __VLS_export;
|
|
19
|
+
export default _default;
|
|
20
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
21
|
+
new (): {
|
|
22
|
+
$slots: S;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
defineProps({
|
|
3
|
+
title: { type: String, required: true },
|
|
4
|
+
description: { type: String, required: false },
|
|
5
|
+
loading: { type: Boolean, required: false },
|
|
6
|
+
isEmpty: { type: Boolean, required: false },
|
|
7
|
+
empty: { type: String, required: false },
|
|
8
|
+
to: { type: String, required: false },
|
|
9
|
+
toLabel: { type: String, required: false }
|
|
10
|
+
});
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<template>
|
|
14
|
+
<section class="bg-elevated/50 ring-default flex h-full flex-col overflow-hidden rounded-lg ring">
|
|
15
|
+
<div class="border-default flex items-center justify-between gap-3 border-b px-4 py-3">
|
|
16
|
+
<div class="min-w-0">
|
|
17
|
+
<h2 class="text-highlighted text-sm font-semibold">
|
|
18
|
+
{{ title }}
|
|
19
|
+
</h2>
|
|
20
|
+
<p
|
|
21
|
+
v-if="description"
|
|
22
|
+
class="text-muted text-xs"
|
|
23
|
+
>
|
|
24
|
+
{{ description }}
|
|
25
|
+
</p>
|
|
26
|
+
</div>
|
|
27
|
+
<div
|
|
28
|
+
v-if="$slots.actions"
|
|
29
|
+
class="flex items-center gap-2"
|
|
30
|
+
>
|
|
31
|
+
<slot name="actions" />
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
<div class="flex-1 px-4">
|
|
35
|
+
<div
|
|
36
|
+
v-if="loading"
|
|
37
|
+
class="space-y-3 py-4"
|
|
38
|
+
>
|
|
39
|
+
<USkeleton class="h-4 w-2/3" />
|
|
40
|
+
<USkeleton class="h-4 w-1/2" />
|
|
41
|
+
<USkeleton class="h-4 w-3/5" />
|
|
42
|
+
</div>
|
|
43
|
+
<p
|
|
44
|
+
v-else-if="isEmpty"
|
|
45
|
+
class="text-muted py-6 text-center text-sm"
|
|
46
|
+
>
|
|
47
|
+
{{ empty }}
|
|
48
|
+
</p>
|
|
49
|
+
<ul
|
|
50
|
+
v-else
|
|
51
|
+
class="divide-default divide-y text-sm [&>li]:py-2.5"
|
|
52
|
+
>
|
|
53
|
+
<slot />
|
|
54
|
+
</ul>
|
|
55
|
+
</div>
|
|
56
|
+
<div
|
|
57
|
+
v-if="to"
|
|
58
|
+
class="border-default border-t px-4 py-2.5"
|
|
59
|
+
>
|
|
60
|
+
<UButton
|
|
61
|
+
:to="to"
|
|
62
|
+
variant="link"
|
|
63
|
+
color="neutral"
|
|
64
|
+
size="xs"
|
|
65
|
+
class="p-0"
|
|
66
|
+
trailing-icon="i-lucide-arrow-right"
|
|
67
|
+
:label="toLabel"
|
|
68
|
+
/>
|
|
69
|
+
</div>
|
|
70
|
+
</section>
|
|
71
|
+
</template>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
title: string;
|
|
3
|
+
description?: string;
|
|
4
|
+
loading?: boolean;
|
|
5
|
+
isEmpty?: boolean;
|
|
6
|
+
empty?: string;
|
|
7
|
+
to?: string;
|
|
8
|
+
toLabel?: string;
|
|
9
|
+
};
|
|
10
|
+
declare var __VLS_1: {}, __VLS_18: {};
|
|
11
|
+
type __VLS_Slots = {} & {
|
|
12
|
+
actions?: (props: typeof __VLS_1) => any;
|
|
13
|
+
} & {
|
|
14
|
+
default?: (props: typeof __VLS_18) => any;
|
|
15
|
+
};
|
|
16
|
+
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
17
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
18
|
+
declare const _default: typeof __VLS_export;
|
|
19
|
+
export default _default;
|
|
20
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
21
|
+
new (): {
|
|
22
|
+
$slots: S;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
@@ -6,19 +6,19 @@ type __VLS_Props = {
|
|
|
6
6
|
to: string;
|
|
7
7
|
};
|
|
8
8
|
};
|
|
9
|
-
declare var
|
|
9
|
+
declare var __VLS_1: {}, __VLS_8: {}, __VLS_10: {}, __VLS_12: {}, __VLS_14: {}, __VLS_16: {};
|
|
10
10
|
type __VLS_Slots = {} & {
|
|
11
|
-
headline?: (props: typeof
|
|
11
|
+
headline?: (props: typeof __VLS_1) => any;
|
|
12
12
|
} & {
|
|
13
|
-
title?: (props: typeof
|
|
13
|
+
title?: (props: typeof __VLS_8) => any;
|
|
14
14
|
} & {
|
|
15
|
-
description?: (props: typeof
|
|
15
|
+
description?: (props: typeof __VLS_10) => any;
|
|
16
16
|
} & {
|
|
17
|
-
|
|
17
|
+
default?: (props: typeof __VLS_12) => any;
|
|
18
18
|
} & {
|
|
19
|
-
|
|
19
|
+
links?: (props: typeof __VLS_14) => any;
|
|
20
20
|
} & {
|
|
21
|
-
|
|
21
|
+
actions?: (props: typeof __VLS_16) => any;
|
|
22
22
|
};
|
|
23
23
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
24
24
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
@@ -7,19 +7,12 @@ defineProps({
|
|
|
7
7
|
</script>
|
|
8
8
|
|
|
9
9
|
<template>
|
|
10
|
-
<
|
|
11
|
-
|
|
12
|
-
:description="description"
|
|
13
|
-
:ui="{ root: 'pt-0 pb-6 mb-6', title: 'text-2xl sm:text-3xl' }"
|
|
14
|
-
>
|
|
15
|
-
<template
|
|
16
|
-
v-if="back || $slots.headline"
|
|
17
|
-
#headline
|
|
18
|
-
>
|
|
10
|
+
<header class="mb-5 flex flex-wrap items-end justify-between gap-x-4 gap-y-3">
|
|
11
|
+
<div class="min-w-0 space-y-1">
|
|
19
12
|
<slot name="headline">
|
|
20
13
|
<UButton
|
|
21
14
|
v-if="back"
|
|
22
|
-
class="p-
|
|
15
|
+
class="-ml-1 p-1"
|
|
23
16
|
color="neutral"
|
|
24
17
|
variant="link"
|
|
25
18
|
size="sm"
|
|
@@ -28,31 +21,28 @@ defineProps({
|
|
|
28
21
|
:label="back.label"
|
|
29
22
|
/>
|
|
30
23
|
</slot>
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
<slot
|
|
45
|
-
</
|
|
46
|
-
|
|
47
|
-
<template
|
|
24
|
+
<h1 class="text-highlighted text-xl font-semibold tracking-tight text-pretty sm:text-2xl">
|
|
25
|
+
<slot name="title">
|
|
26
|
+
{{ title }}
|
|
27
|
+
</slot>
|
|
28
|
+
</h1>
|
|
29
|
+
<p
|
|
30
|
+
v-if="description || $slots.description"
|
|
31
|
+
class="text-muted text-sm text-pretty"
|
|
32
|
+
>
|
|
33
|
+
<slot name="description">
|
|
34
|
+
{{ description }}
|
|
35
|
+
</slot>
|
|
36
|
+
</p>
|
|
37
|
+
<slot />
|
|
38
|
+
</div>
|
|
39
|
+
<div
|
|
48
40
|
v-if="$slots.links || $slots.actions"
|
|
49
|
-
|
|
41
|
+
class="flex flex-wrap items-center gap-2"
|
|
50
42
|
>
|
|
51
43
|
<slot name="links">
|
|
52
44
|
<slot name="actions" />
|
|
53
45
|
</slot>
|
|
54
|
-
</
|
|
55
|
-
|
|
56
|
-
<slot />
|
|
57
|
-
</UPageHeader>
|
|
46
|
+
</div>
|
|
47
|
+
</header>
|
|
58
48
|
</template>
|
|
@@ -6,19 +6,19 @@ type __VLS_Props = {
|
|
|
6
6
|
to: string;
|
|
7
7
|
};
|
|
8
8
|
};
|
|
9
|
-
declare var
|
|
9
|
+
declare var __VLS_1: {}, __VLS_8: {}, __VLS_10: {}, __VLS_12: {}, __VLS_14: {}, __VLS_16: {};
|
|
10
10
|
type __VLS_Slots = {} & {
|
|
11
|
-
headline?: (props: typeof
|
|
11
|
+
headline?: (props: typeof __VLS_1) => any;
|
|
12
12
|
} & {
|
|
13
|
-
title?: (props: typeof
|
|
13
|
+
title?: (props: typeof __VLS_8) => any;
|
|
14
14
|
} & {
|
|
15
|
-
description?: (props: typeof
|
|
15
|
+
description?: (props: typeof __VLS_10) => any;
|
|
16
16
|
} & {
|
|
17
|
-
|
|
17
|
+
default?: (props: typeof __VLS_12) => any;
|
|
18
18
|
} & {
|
|
19
|
-
|
|
19
|
+
links?: (props: typeof __VLS_14) => any;
|
|
20
20
|
} & {
|
|
21
|
-
|
|
21
|
+
actions?: (props: typeof __VLS_16) => any;
|
|
22
22
|
};
|
|
23
23
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
24
24
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
@@ -7,14 +7,14 @@ defineProps({
|
|
|
7
7
|
</script>
|
|
8
8
|
|
|
9
9
|
<template>
|
|
10
|
-
<section class="
|
|
10
|
+
<section class="bg-elevated/50 ring-default ring overflow-hidden rounded-lg">
|
|
11
11
|
<div
|
|
12
12
|
v-if="title || $slots.header || $slots.actions"
|
|
13
|
-
class="border-default flex flex-wrap items-center justify-between gap-3 border-b px-4 py-3"
|
|
13
|
+
class="border-default flex flex-wrap items-center justify-between gap-3 border-b px-4 py-3.5"
|
|
14
14
|
>
|
|
15
15
|
<slot name="header">
|
|
16
16
|
<div class="min-w-0">
|
|
17
|
-
<h2 class="text-sm font-semibold">
|
|
17
|
+
<h2 class="text-highlighted text-sm font-semibold">
|
|
18
18
|
{{ title }}
|
|
19
19
|
</h2>
|
|
20
20
|
<p
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
type __VLS_Props = {
|
|
2
2
|
label: string;
|
|
3
|
+
icon?: string;
|
|
4
|
+
to?: string;
|
|
3
5
|
loading?: boolean;
|
|
4
6
|
empty?: string;
|
|
5
7
|
};
|
|
6
|
-
declare var
|
|
8
|
+
declare var __VLS_23: {};
|
|
7
9
|
type __VLS_Slots = {} & {
|
|
8
|
-
default?: (props: typeof
|
|
10
|
+
default?: (props: typeof __VLS_23) => any;
|
|
9
11
|
};
|
|
10
12
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
11
13
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
@@ -1,34 +1,53 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
defineProps({
|
|
3
3
|
label: { type: String, required: true },
|
|
4
|
+
icon: { type: String, required: false },
|
|
5
|
+
to: { type: String, required: false },
|
|
4
6
|
loading: { type: Boolean, required: false },
|
|
5
7
|
empty: { type: String, required: false }
|
|
6
8
|
});
|
|
9
|
+
const NuxtLink = resolveComponent("NuxtLink");
|
|
7
10
|
</script>
|
|
8
11
|
|
|
9
12
|
<template>
|
|
10
|
-
<
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
<component
|
|
14
|
+
:is="to ? NuxtLink : 'section'"
|
|
15
|
+
:to="to"
|
|
16
|
+
class="bg-elevated/50 ring-default flex h-full items-start gap-3 overflow-hidden rounded-lg p-4 ring"
|
|
17
|
+
:class="to && 'hover:bg-elevated transition-colors'"
|
|
18
|
+
>
|
|
19
|
+
<span
|
|
20
|
+
v-if="icon"
|
|
21
|
+
class="bg-primary/10 ring-primary/25 text-primary flex shrink-0 items-center justify-center rounded-full p-2.5 ring ring-inset"
|
|
17
22
|
>
|
|
18
|
-
<
|
|
19
|
-
|
|
23
|
+
<UIcon
|
|
24
|
+
:name="icon"
|
|
25
|
+
class="size-5"
|
|
26
|
+
/>
|
|
27
|
+
</span>
|
|
28
|
+
<div class="flex min-w-0 flex-1 flex-col gap-1.5">
|
|
29
|
+
<h2 class="text-muted text-xs font-normal uppercase">
|
|
30
|
+
{{ label }}
|
|
31
|
+
</h2>
|
|
32
|
+
<div
|
|
33
|
+
v-if="loading"
|
|
34
|
+
class="space-y-2"
|
|
35
|
+
>
|
|
36
|
+
<USkeleton class="h-5 w-2/3" />
|
|
37
|
+
<USkeleton class="h-4 w-1/3" />
|
|
38
|
+
</div>
|
|
39
|
+
<div
|
|
40
|
+
v-else-if="$slots.default"
|
|
41
|
+
class="text-highlighted flex flex-wrap items-center gap-2 text-lg font-semibold"
|
|
42
|
+
>
|
|
43
|
+
<slot />
|
|
44
|
+
</div>
|
|
45
|
+
<p
|
|
46
|
+
v-else
|
|
47
|
+
class="text-muted text-sm"
|
|
48
|
+
>
|
|
49
|
+
{{ empty }}
|
|
50
|
+
</p>
|
|
20
51
|
</div>
|
|
21
|
-
|
|
22
|
-
v-else-if="$slots.default"
|
|
23
|
-
class="flex flex-wrap items-center gap-2 text-sm"
|
|
24
|
-
>
|
|
25
|
-
<slot />
|
|
26
|
-
</div>
|
|
27
|
-
<p
|
|
28
|
-
v-else
|
|
29
|
-
class="text-muted text-sm"
|
|
30
|
-
>
|
|
31
|
-
{{ empty }}
|
|
32
|
-
</p>
|
|
33
|
-
</section>
|
|
52
|
+
</component>
|
|
34
53
|
</template>
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
type __VLS_Props = {
|
|
2
2
|
label: string;
|
|
3
|
+
icon?: string;
|
|
4
|
+
to?: string;
|
|
3
5
|
loading?: boolean;
|
|
4
6
|
empty?: string;
|
|
5
7
|
};
|
|
6
|
-
declare var
|
|
8
|
+
declare var __VLS_23: {};
|
|
7
9
|
type __VLS_Slots = {} & {
|
|
8
|
-
default?: (props: typeof
|
|
10
|
+
default?: (props: typeof __VLS_23) => any;
|
|
9
11
|
};
|
|
10
12
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
11
13
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
@@ -29,77 +29,100 @@ const sidebarOpen = ref(false);
|
|
|
29
29
|
watch(() => route.path, () => {
|
|
30
30
|
sidebarOpen.value = false;
|
|
31
31
|
});
|
|
32
|
+
const searchGroups = computed(() => [{
|
|
33
|
+
id: "admin-nav",
|
|
34
|
+
label: t("search.goTo", "Go to"),
|
|
35
|
+
items: links.value.map(({ label, icon, to }) => ({ id: to ?? label, label, icon, to }))
|
|
36
|
+
}]);
|
|
32
37
|
</script>
|
|
33
38
|
|
|
34
39
|
<template>
|
|
35
|
-
<
|
|
36
|
-
<
|
|
37
|
-
|
|
38
|
-
:
|
|
40
|
+
<UDashboardGroup unit="rem">
|
|
41
|
+
<UDashboardSidebar
|
|
42
|
+
id="admin"
|
|
43
|
+
v-model:open="sidebarOpen"
|
|
44
|
+
collapsible
|
|
45
|
+
resizable
|
|
46
|
+
class="bg-elevated/25"
|
|
47
|
+
:ui="{ footer: 'lg:border-t lg:border-default' }"
|
|
39
48
|
>
|
|
40
|
-
<
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
</
|
|
55
|
-
</
|
|
49
|
+
<template #header="{ collapsed }">
|
|
50
|
+
<NuxtLink
|
|
51
|
+
:to="home"
|
|
52
|
+
class="flex min-w-0 items-center gap-2"
|
|
53
|
+
>
|
|
54
|
+
<span class="bg-primary text-inverted flex size-7 shrink-0 items-center justify-center rounded-lg">
|
|
55
|
+
<UIcon
|
|
56
|
+
name="i-lucide-layout-dashboard"
|
|
57
|
+
class="size-4"
|
|
58
|
+
/>
|
|
59
|
+
</span>
|
|
60
|
+
<span
|
|
61
|
+
v-if="!collapsed"
|
|
62
|
+
class="text-highlighted truncate font-semibold"
|
|
63
|
+
>{{ t("title", "Administration") }}</span>
|
|
64
|
+
</NuxtLink>
|
|
65
|
+
</template>
|
|
66
|
+
|
|
67
|
+
<template #default="{ collapsed }">
|
|
68
|
+
<UDashboardSearchButton
|
|
69
|
+
:collapsed="collapsed"
|
|
70
|
+
class="bg-transparent ring-default"
|
|
71
|
+
/>
|
|
56
72
|
|
|
57
73
|
<UNavigationMenu
|
|
58
|
-
|
|
74
|
+
:collapsed="collapsed"
|
|
59
75
|
:items="links"
|
|
76
|
+
orientation="vertical"
|
|
77
|
+
tooltip
|
|
60
78
|
:aria-label="t('nav.label', 'Admin navigation')"
|
|
61
79
|
/>
|
|
80
|
+
</template>
|
|
81
|
+
|
|
82
|
+
<template #footer="{ collapsed }">
|
|
83
|
+
<UButton
|
|
84
|
+
to="/"
|
|
85
|
+
color="neutral"
|
|
86
|
+
variant="ghost"
|
|
87
|
+
icon="i-lucide-arrow-left"
|
|
88
|
+
:label="collapsed ? void 0 : t('backToSite', 'Back to the site')"
|
|
89
|
+
:square="collapsed"
|
|
90
|
+
block
|
|
91
|
+
class="justify-start"
|
|
92
|
+
/>
|
|
93
|
+
</template>
|
|
94
|
+
</UDashboardSidebar>
|
|
62
95
|
|
|
63
|
-
|
|
64
|
-
<UButton
|
|
65
|
-
to="/"
|
|
66
|
-
color="neutral"
|
|
67
|
-
variant="ghost"
|
|
68
|
-
icon="i-lucide-arrow-left"
|
|
69
|
-
:label="t('backToSite', 'Back to the site')"
|
|
70
|
-
block
|
|
71
|
-
/>
|
|
72
|
-
</template>
|
|
73
|
-
</UDashboardSidebar>
|
|
96
|
+
<UDashboardSearch :groups="searchGroups" />
|
|
74
97
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
98
|
+
<UDashboardPanel id="admin-main">
|
|
99
|
+
<template #header>
|
|
100
|
+
<UDashboardNavbar
|
|
101
|
+
:title="title"
|
|
102
|
+
:ui="{ right: 'gap-2' }"
|
|
103
|
+
>
|
|
104
|
+
<template #leading>
|
|
105
|
+
<UDashboardSidebarCollapse />
|
|
106
|
+
</template>
|
|
107
|
+
<template #right>
|
|
108
|
+
<component
|
|
109
|
+
:is="optional(LanguageSelect)"
|
|
110
|
+
v-if="optional(LanguageSelect) && multiLocale"
|
|
111
|
+
/>
|
|
112
|
+
<UColorModeButton />
|
|
113
|
+
<component
|
|
114
|
+
:is="optional(AccountMenu)"
|
|
115
|
+
v-if="optional(AccountMenu)"
|
|
116
|
+
/>
|
|
117
|
+
</template>
|
|
118
|
+
</UDashboardNavbar>
|
|
119
|
+
</template>
|
|
94
120
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
</UDashboardPanel>
|
|
103
|
-
</UDashboardGroup>
|
|
104
|
-
</div>
|
|
121
|
+
<template #body>
|
|
122
|
+
<div class="admin-content mx-auto w-full max-w-7xl">
|
|
123
|
+
<slot />
|
|
124
|
+
</div>
|
|
125
|
+
</template>
|
|
126
|
+
</UDashboardPanel>
|
|
127
|
+
</UDashboardGroup>
|
|
105
128
|
</template>
|
|
@@ -3,6 +3,13 @@ defineOptions({ name: "AdminDashboardPage" });
|
|
|
3
3
|
const t = useModuleT("admin");
|
|
4
4
|
const { data: menu } = await useMenu("admin.nav");
|
|
5
5
|
const items = computed(() => (menu.value ?? []).filter((item) => item.id !== "admin-dashboard"));
|
|
6
|
+
const [{ data: top }, { data: main }, { data: side }] = await Promise.all([
|
|
7
|
+
useWidgets("admin.dashboard.top"),
|
|
8
|
+
useWidgets("admin.dashboard"),
|
|
9
|
+
useWidgets("admin.dashboard.side")
|
|
10
|
+
]);
|
|
11
|
+
const hasWidgets = computed(() => !!(top.value?.length || main.value?.length || side.value?.length));
|
|
12
|
+
const stack = "flex flex-col gap-4";
|
|
6
13
|
</script>
|
|
7
14
|
|
|
8
15
|
<template>
|
|
@@ -11,28 +18,47 @@ const items = computed(() => (menu.value ?? []).filter((item) => item.id !== "ad
|
|
|
11
18
|
:title="t('dashboard.title', 'Dashboard')"
|
|
12
19
|
:description="t('dashboard.description', 'Overview and shortcuts for administrators.')"
|
|
13
20
|
/>
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
21
|
+
|
|
22
|
+
<template v-if="hasWidgets">
|
|
23
|
+
<WidgetArea name="admin.dashboard.top" />
|
|
24
|
+
<div class="grid gap-4 lg:grid-cols-3">
|
|
25
|
+
<WidgetArea
|
|
26
|
+
name="admin.dashboard"
|
|
27
|
+
:grid-class="`${stack} ${side?.length ? 'lg:col-span-2' : 'lg:col-span-3'}`"
|
|
28
|
+
/>
|
|
29
|
+
<WidgetArea
|
|
30
|
+
name="admin.dashboard.side"
|
|
31
|
+
:grid-class="stack"
|
|
32
|
+
/>
|
|
33
|
+
</div>
|
|
34
|
+
</template>
|
|
35
|
+
|
|
36
|
+
<template v-else>
|
|
37
|
+
<UPageGrid
|
|
38
|
+
v-if="items.length"
|
|
39
|
+
class="lg:grid-cols-3"
|
|
40
|
+
>
|
|
41
|
+
<UPageCard
|
|
42
|
+
v-for="item in items"
|
|
43
|
+
:key="item.id"
|
|
44
|
+
:title="item.label"
|
|
45
|
+
:description="item.description"
|
|
46
|
+
:icon="item.icon"
|
|
47
|
+
:to="item.to"
|
|
48
|
+
variant="subtle"
|
|
49
|
+
:ui="{
|
|
50
|
+
container: 'gap-y-1.5',
|
|
51
|
+
wrapper: 'items-start',
|
|
52
|
+
leading: 'p-2.5 rounded-full bg-primary/10 ring ring-inset ring-primary/25 flex-col'
|
|
53
|
+
}"
|
|
54
|
+
/>
|
|
55
|
+
</UPageGrid>
|
|
56
|
+
<p
|
|
57
|
+
v-else
|
|
58
|
+
class="text-muted"
|
|
59
|
+
>
|
|
60
|
+
{{ t("dashboard.empty", "Nothing here yet. Modules add their admin pages and widgets automatically.") }}
|
|
61
|
+
</p>
|
|
62
|
+
</template>
|
|
37
63
|
</AdminPage>
|
|
38
64
|
</template>
|
|
@@ -8,7 +8,16 @@ import type { WidgetDefinition } from '@frigear-nu/widget/utils';
|
|
|
8
8
|
export declare const adminRoute: (nuxt: Nuxt, ...segments: string[]) => string;
|
|
9
9
|
export declare const ADMIN_NAV_MENU = "admin.nav";
|
|
10
10
|
export declare const ADMIN_DASHBOARD_AREA = "admin.dashboard";
|
|
11
|
+
/** The stat row above the dashboard content. */
|
|
12
|
+
export declare const ADMIN_DASHBOARD_TOP_AREA = "admin.dashboard.top";
|
|
13
|
+
/** The narrow column next to the main dashboard content. */
|
|
14
|
+
export declare const ADMIN_DASHBOARD_SIDE_AREA = "admin.dashboard.side";
|
|
15
|
+
export type AdminDashboardPosition = 'top' | 'main' | 'side';
|
|
11
16
|
/** Adds entries to the admin sidebar (the `admin.nav` menu of `@frigear-nu/menu`). Call from a module's `setup()`. */
|
|
12
17
|
export declare const extendAdminNav: (nuxt: Nuxt, items: MenuItem | MenuItem[]) => void;
|
|
13
|
-
/**
|
|
14
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Adds widgets to the admin dashboard (an area of `@frigear-nu/widget`). Call from a module's `setup()`.
|
|
20
|
+
* `position` picks the area: `top` is the stat row (use `AdminStatCard`), `main` the content column (the default),
|
|
21
|
+
* `side` the narrow column.
|
|
22
|
+
*/
|
|
23
|
+
export declare const extendAdminDashboard: (nuxt: Nuxt, widgets: WidgetDefinition | WidgetDefinition[], position?: AdminDashboardPosition) => void;
|
|
@@ -6,5 +6,12 @@ export const adminRoute = (nuxt, ...segments) => {
|
|
|
6
6
|
};
|
|
7
7
|
export const ADMIN_NAV_MENU = "admin.nav";
|
|
8
8
|
export const ADMIN_DASHBOARD_AREA = "admin.dashboard";
|
|
9
|
+
export const ADMIN_DASHBOARD_TOP_AREA = "admin.dashboard.top";
|
|
10
|
+
export const ADMIN_DASHBOARD_SIDE_AREA = "admin.dashboard.side";
|
|
11
|
+
const DASHBOARD_AREAS = {
|
|
12
|
+
top: ADMIN_DASHBOARD_TOP_AREA,
|
|
13
|
+
main: ADMIN_DASHBOARD_AREA,
|
|
14
|
+
side: ADMIN_DASHBOARD_SIDE_AREA
|
|
15
|
+
};
|
|
9
16
|
export const extendAdminNav = (nuxt, items) => extendMenu(nuxt, ADMIN_NAV_MENU, items);
|
|
10
|
-
export const extendAdminDashboard = (nuxt, widgets) => extendWidgets(nuxt,
|
|
17
|
+
export const extendAdminDashboard = (nuxt, widgets, position = "main") => extendWidgets(nuxt, DASHBOARD_AREAS[position], widgets);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frigear-nu/admin",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "Generic admin area: shell layout, guarded dashboard and navigation that other modules contribute to through @frigear-nu/menu and @frigear-nu/widget.",
|
|
5
5
|
"homepage": "https://docs.frigear.dev/modules/admin",
|
|
6
6
|
"repository": {
|
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
"playground"
|
|
31
31
|
],
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@frigear-nu/menu": "0.0.
|
|
34
|
-
"@frigear-nu/module-kit": "0.
|
|
35
|
-
"@frigear-nu/widget": "0.0.
|
|
33
|
+
"@frigear-nu/menu": "0.0.10",
|
|
34
|
+
"@frigear-nu/module-kit": "1.0.0",
|
|
35
|
+
"@frigear-nu/widget": "0.0.3",
|
|
36
36
|
"@nuxt/kit": "^4.6.0",
|
|
37
37
|
"nuxt-auth-utils": "^0.5.30"
|
|
38
38
|
},
|