@frigear-nu/admin 0.0.0-stage → 0.0.1
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 +4 -2
- package/dist/module.d.mts +37 -0
- package/dist/module.json +9 -0
- package/dist/module.mjs +83 -0
- package/dist/runtime/app/components/AdminEmpty.d.vue.ts +18 -0
- package/dist/runtime/app/components/AdminEmpty.vue +24 -0
- package/dist/runtime/app/components/AdminEmpty.vue.d.ts +18 -0
- package/dist/runtime/app/components/AdminKeyValue.d.vue.ts +24 -0
- package/dist/runtime/app/components/AdminKeyValue.vue +32 -0
- package/dist/runtime/app/components/AdminKeyValue.vue.d.ts +24 -0
- package/dist/runtime/app/components/AdminPage.d.vue.ts +13 -0
- package/dist/runtime/app/components/AdminPage.vue +5 -0
- package/dist/runtime/app/components/AdminPage.vue.d.ts +13 -0
- package/dist/runtime/app/components/AdminPageHeader.d.vue.ts +31 -0
- package/dist/runtime/app/components/AdminPageHeader.vue +58 -0
- package/dist/runtime/app/components/AdminPageHeader.vue.d.ts +31 -0
- package/dist/runtime/app/components/AdminSection.d.vue.ts +26 -0
- package/dist/runtime/app/components/AdminSection.vue +55 -0
- package/dist/runtime/app/components/AdminSection.vue.d.ts +26 -0
- package/dist/runtime/app/components/AdminStatCard.d.vue.ts +18 -0
- package/dist/runtime/app/components/AdminStatCard.vue +34 -0
- package/dist/runtime/app/components/AdminStatCard.vue.d.ts +18 -0
- package/dist/runtime/app/components/AdminTable.d.vue.ts +28 -0
- package/dist/runtime/app/components/AdminTable.vue +39 -0
- package/dist/runtime/app/components/AdminTable.vue.d.ts +28 -0
- package/dist/runtime/app/components/AdminToolbar.d.vue.ts +13 -0
- package/dist/runtime/app/components/AdminToolbar.vue +5 -0
- package/dist/runtime/app/components/AdminToolbar.vue.d.ts +13 -0
- package/dist/runtime/app/layouts/admin.d.vue.ts +13 -0
- package/dist/runtime/app/layouts/admin.vue +105 -0
- package/dist/runtime/app/layouts/admin.vue.d.ts +13 -0
- package/dist/runtime/app/pages/dashboard.d.vue.ts +3 -0
- package/dist/runtime/app/pages/dashboard.vue +38 -0
- package/dist/runtime/app/pages/dashboard.vue.d.ts +3 -0
- package/dist/runtime/i18n/lang/da.json +18 -0
- package/dist/runtime/i18n/lang/en.json +18 -0
- package/dist/runtime/utils/index.d.ts +14 -0
- package/dist/runtime/utils/index.js +10 -0
- package/dist/types.d.mts +3 -0
- package/package.json +74 -3
package/README.md
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @frigear-nu/admin
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Generic admin area: an `admin` layout with a sidebar (the `admin.nav` menu), a guarded dashboard (the `admin.dashboard` widget area) and `<AdminPageHeader>`. Other modules register their admin pages and widgets into it.
|
|
4
|
+
|
|
5
|
+
See the [documentation](../../apps/docs/content/4.modules/02.admin.md) for usage.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import * as _nuxt_schema from '@nuxt/schema';
|
|
2
|
+
|
|
3
|
+
type ModuleOptions = {
|
|
4
|
+
settings?: {
|
|
5
|
+
/** Path prefix of the admin area. Defaults to `/admin`. */
|
|
6
|
+
base?: string;
|
|
7
|
+
/**
|
|
8
|
+
* The dashboard page at `base`. `true`/omitted = on, `false` = off (modules' own pages still get the layout),
|
|
9
|
+
* or a custom path.
|
|
10
|
+
*/
|
|
11
|
+
dashboard?: boolean | string;
|
|
12
|
+
/**
|
|
13
|
+
* Roles allowed into the admin area, applied as an `@frigear-nu/authorization` route rule on `<base>/**`
|
|
14
|
+
* (only when that module is installed). Defaults to `['admin']`. Pass `false` to set no rule and guard the area yourself.
|
|
15
|
+
*/
|
|
16
|
+
roles?: string[] | false;
|
|
17
|
+
/** Applies the `admin` layout to every page under `base` that has no layout of its own. Defaults to `true`. */
|
|
18
|
+
layout?: boolean;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
type PublicModuleOptions = {
|
|
22
|
+
base: string;
|
|
23
|
+
dashboard?: string;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
declare module 'nuxt/schema' {
|
|
27
|
+
interface NuxtConfig {
|
|
28
|
+
admin?: ModuleOptions;
|
|
29
|
+
}
|
|
30
|
+
interface PublicRuntimeConfig {
|
|
31
|
+
admin?: PublicModuleOptions;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
|
|
35
|
+
|
|
36
|
+
export { _default as default };
|
|
37
|
+
export type { ModuleOptions, PublicModuleOptions };
|
package/dist/module.json
ADDED
package/dist/module.mjs
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { defineNuxtModule, createResolver, addLayout, addComponent, hasNuxtModule, extendPages } from '@nuxt/kit';
|
|
2
|
+
import { registerModule, registerTailwindSource, registerLocales } from '@frigear-nu/module-kit/utils';
|
|
3
|
+
import { extendMenu } from '@frigear-nu/menu/utils';
|
|
4
|
+
|
|
5
|
+
const trimSlash = (path) => path.length > 1 ? path.replace(/\/+$/, "") : path;
|
|
6
|
+
const module$1 = defineNuxtModule({
|
|
7
|
+
meta: { name: "@frigear-nu/admin", configKey: "admin" },
|
|
8
|
+
defaults: {
|
|
9
|
+
settings: {}
|
|
10
|
+
},
|
|
11
|
+
moduleDependencies: {
|
|
12
|
+
"@frigear-nu/module-kit": { version: ">=0.0.0" },
|
|
13
|
+
// The signed-in user is shown in the header (`useUserSession`).
|
|
14
|
+
"nuxt-auth-utils": { version: ">=0.0.1" },
|
|
15
|
+
"@frigear-nu/menu": { version: ">=0.0.0" },
|
|
16
|
+
"@frigear-nu/widget": { version: ">=0.0.0" },
|
|
17
|
+
"@frigear-nu/authorization": { version: ">=0.0.0", optional: true }
|
|
18
|
+
},
|
|
19
|
+
setup(options, nuxt) {
|
|
20
|
+
registerModule("@frigear-nu/admin", nuxt);
|
|
21
|
+
const resolver = createResolver(import.meta.url);
|
|
22
|
+
registerTailwindSource(nuxt, resolver.resolve("./runtime/app"));
|
|
23
|
+
registerLocales(nuxt, resolver.resolve("./runtime/i18n/lang"));
|
|
24
|
+
const settings = options.settings ?? {};
|
|
25
|
+
const base = trimSlash(settings.base ?? "/admin");
|
|
26
|
+
const dashboardPath = settings.dashboard === false ? void 0 : typeof settings.dashboard === "string" ? settings.dashboard : base;
|
|
27
|
+
nuxt.options.runtimeConfig.public.admin = { base, dashboard: dashboardPath };
|
|
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"]) {
|
|
30
|
+
addComponent({ name, filePath: resolver.resolve(`./runtime/app/components/${name}.vue`) });
|
|
31
|
+
}
|
|
32
|
+
const prefixes = ["", ...(nuxt.options.i18n?.locales ?? []).map((locale) => typeof locale === "string" ? locale : locale.code).map((code) => `/${code}`)];
|
|
33
|
+
const bases = prefixes.map((prefix) => `${prefix}${base}`);
|
|
34
|
+
if (settings.roles !== false) {
|
|
35
|
+
if (hasNuxtModule("@frigear-nu/authorization")) {
|
|
36
|
+
const rules = nuxt.options.routeRules ||= {};
|
|
37
|
+
for (const pattern of bases.map((prefixed) => `${prefixed}/**`)) {
|
|
38
|
+
rules[pattern] = { roles: settings.roles ?? ["admin"], ...rules[pattern] };
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
console.warn("[@frigear-nu/admin] @frigear-nu/authorization is not installed: the admin pages are not guarded on the client. Install it or set `admin.settings.roles: false` and guard the area yourself.");
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (settings.layout !== false) {
|
|
45
|
+
const rules = nuxt.options.routeRules ||= {};
|
|
46
|
+
for (const prefixed of bases) {
|
|
47
|
+
for (const pattern of [prefixed, `${prefixed}/**`]) {
|
|
48
|
+
rules[pattern] = { appLayout: "admin", ...rules[pattern] };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (dashboardPath && settings.roles !== false) {
|
|
53
|
+
extendMenu(nuxt, "account.dropdown", {
|
|
54
|
+
id: "admin",
|
|
55
|
+
label: "Admin",
|
|
56
|
+
labelKey: "admin.menu.label",
|
|
57
|
+
description: "Administration",
|
|
58
|
+
descriptionKey: "admin.menu.description",
|
|
59
|
+
icon: "i-lucide-shield-check",
|
|
60
|
+
to: dashboardPath,
|
|
61
|
+
order: 90,
|
|
62
|
+
meta: { roles: settings.roles ?? ["admin"] }
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
if (dashboardPath) {
|
|
66
|
+
extendMenu(nuxt, "admin.nav", {
|
|
67
|
+
id: "admin-dashboard",
|
|
68
|
+
label: "Dashboard",
|
|
69
|
+
labelKey: "admin.dashboard.title",
|
|
70
|
+
icon: "i-lucide-layout-dashboard",
|
|
71
|
+
to: dashboardPath,
|
|
72
|
+
order: 0
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
extendPages((pages) => {
|
|
76
|
+
if (dashboardPath && !pages.some((page) => page.path === dashboardPath)) {
|
|
77
|
+
pages.push({ name: "admin-dashboard", path: dashboardPath, file: resolver.resolve("./runtime/app/pages/dashboard.vue") });
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
export { module$1 as default };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
icon?: string;
|
|
3
|
+
title: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
};
|
|
6
|
+
declare var __VLS_9: {};
|
|
7
|
+
type __VLS_Slots = {} & {
|
|
8
|
+
default?: (props: typeof __VLS_9) => any;
|
|
9
|
+
};
|
|
10
|
+
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
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
12
|
+
declare const _default: typeof __VLS_export;
|
|
13
|
+
export default _default;
|
|
14
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
15
|
+
new (): {
|
|
16
|
+
$slots: S;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
defineProps({
|
|
3
|
+
icon: { type: String, required: false },
|
|
4
|
+
title: { type: String, required: true },
|
|
5
|
+
description: { type: String, required: false }
|
|
6
|
+
});
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<template>
|
|
10
|
+
<UEmpty
|
|
11
|
+
:icon="icon"
|
|
12
|
+
:title="title"
|
|
13
|
+
:description="description"
|
|
14
|
+
variant="naked"
|
|
15
|
+
class="py-10"
|
|
16
|
+
>
|
|
17
|
+
<template
|
|
18
|
+
v-if="$slots.default"
|
|
19
|
+
#actions
|
|
20
|
+
>
|
|
21
|
+
<slot />
|
|
22
|
+
</template>
|
|
23
|
+
</UEmpty>
|
|
24
|
+
</template>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
icon?: string;
|
|
3
|
+
title: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
};
|
|
6
|
+
declare var __VLS_9: {};
|
|
7
|
+
type __VLS_Slots = {} & {
|
|
8
|
+
default?: (props: typeof __VLS_9) => any;
|
|
9
|
+
};
|
|
10
|
+
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
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
12
|
+
declare const _default: typeof __VLS_export;
|
|
13
|
+
export default _default;
|
|
14
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
15
|
+
new (): {
|
|
16
|
+
$slots: S;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface AdminKeyValueItem {
|
|
2
|
+
key: string;
|
|
3
|
+
label: string;
|
|
4
|
+
value?: string | number | null;
|
|
5
|
+
}
|
|
6
|
+
type __VLS_Props = {
|
|
7
|
+
items: AdminKeyValueItem[];
|
|
8
|
+
loading?: boolean;
|
|
9
|
+
};
|
|
10
|
+
declare var __VLS_7: string, __VLS_8: {
|
|
11
|
+
item: AdminKeyValueItem;
|
|
12
|
+
};
|
|
13
|
+
type __VLS_Slots = {} & {
|
|
14
|
+
[K in NonNullable<typeof __VLS_7>]?: (props: typeof __VLS_8) => 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,32 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
defineProps({
|
|
3
|
+
items: { type: Array, required: true },
|
|
4
|
+
loading: { type: Boolean, required: false }
|
|
5
|
+
});
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<template>
|
|
9
|
+
<dl class="grid grid-cols-[auto_1fr] items-center gap-x-6 gap-y-2 text-sm">
|
|
10
|
+
<template
|
|
11
|
+
v-for="item in items"
|
|
12
|
+
:key="item.key"
|
|
13
|
+
>
|
|
14
|
+
<dt class="text-muted">
|
|
15
|
+
{{ item.label }}
|
|
16
|
+
</dt>
|
|
17
|
+
<dd class="text-highlighted min-w-0 text-right wrap-break-word">
|
|
18
|
+
<USkeleton
|
|
19
|
+
v-if="loading"
|
|
20
|
+
class="ml-auto h-4 w-24"
|
|
21
|
+
/>
|
|
22
|
+
<slot
|
|
23
|
+
v-else
|
|
24
|
+
:name="item.key"
|
|
25
|
+
:item="item"
|
|
26
|
+
>
|
|
27
|
+
{{ item.value ?? "\u2013" }}
|
|
28
|
+
</slot>
|
|
29
|
+
</dd>
|
|
30
|
+
</template>
|
|
31
|
+
</dl>
|
|
32
|
+
</template>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface AdminKeyValueItem {
|
|
2
|
+
key: string;
|
|
3
|
+
label: string;
|
|
4
|
+
value?: string | number | null;
|
|
5
|
+
}
|
|
6
|
+
type __VLS_Props = {
|
|
7
|
+
items: AdminKeyValueItem[];
|
|
8
|
+
loading?: boolean;
|
|
9
|
+
};
|
|
10
|
+
declare var __VLS_7: string, __VLS_8: {
|
|
11
|
+
item: AdminKeyValueItem;
|
|
12
|
+
};
|
|
13
|
+
type __VLS_Slots = {} & {
|
|
14
|
+
[K in NonNullable<typeof __VLS_7>]?: (props: typeof __VLS_8) => 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,13 @@
|
|
|
1
|
+
declare var __VLS_1: {};
|
|
2
|
+
type __VLS_Slots = {} & {
|
|
3
|
+
default?: (props: typeof __VLS_1) => any;
|
|
4
|
+
};
|
|
5
|
+
declare const __VLS_base: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
6
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
7
|
+
declare const _default: typeof __VLS_export;
|
|
8
|
+
export default _default;
|
|
9
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
10
|
+
new (): {
|
|
11
|
+
$slots: S;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
declare var __VLS_1: {};
|
|
2
|
+
type __VLS_Slots = {} & {
|
|
3
|
+
default?: (props: typeof __VLS_1) => any;
|
|
4
|
+
};
|
|
5
|
+
declare const __VLS_base: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
6
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
7
|
+
declare const _default: typeof __VLS_export;
|
|
8
|
+
export default _default;
|
|
9
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
10
|
+
new (): {
|
|
11
|
+
$slots: S;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
title?: string;
|
|
3
|
+
description?: string;
|
|
4
|
+
back?: {
|
|
5
|
+
label: string;
|
|
6
|
+
to: string;
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
declare var __VLS_9: {}, __VLS_17: {}, __VLS_20: {}, __VLS_23: {}, __VLS_25: {}, __VLS_27: {};
|
|
10
|
+
type __VLS_Slots = {} & {
|
|
11
|
+
headline?: (props: typeof __VLS_9) => any;
|
|
12
|
+
} & {
|
|
13
|
+
title?: (props: typeof __VLS_17) => any;
|
|
14
|
+
} & {
|
|
15
|
+
description?: (props: typeof __VLS_20) => any;
|
|
16
|
+
} & {
|
|
17
|
+
links?: (props: typeof __VLS_23) => any;
|
|
18
|
+
} & {
|
|
19
|
+
actions?: (props: typeof __VLS_25) => any;
|
|
20
|
+
} & {
|
|
21
|
+
default?: (props: typeof __VLS_27) => any;
|
|
22
|
+
};
|
|
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
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
25
|
+
declare const _default: typeof __VLS_export;
|
|
26
|
+
export default _default;
|
|
27
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
28
|
+
new (): {
|
|
29
|
+
$slots: S;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
defineProps({
|
|
3
|
+
title: { type: String, required: false },
|
|
4
|
+
description: { type: String, required: false },
|
|
5
|
+
back: { type: Object, required: false }
|
|
6
|
+
});
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<template>
|
|
10
|
+
<UPageHeader
|
|
11
|
+
:title="title"
|
|
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
|
+
>
|
|
19
|
+
<slot name="headline">
|
|
20
|
+
<UButton
|
|
21
|
+
v-if="back"
|
|
22
|
+
class="p-0"
|
|
23
|
+
color="neutral"
|
|
24
|
+
variant="link"
|
|
25
|
+
size="sm"
|
|
26
|
+
icon="i-lucide-arrow-left"
|
|
27
|
+
:to="back.to"
|
|
28
|
+
:label="back.label"
|
|
29
|
+
/>
|
|
30
|
+
</slot>
|
|
31
|
+
</template>
|
|
32
|
+
|
|
33
|
+
<template
|
|
34
|
+
v-if="$slots.title"
|
|
35
|
+
#title
|
|
36
|
+
>
|
|
37
|
+
<slot name="title" />
|
|
38
|
+
</template>
|
|
39
|
+
|
|
40
|
+
<template
|
|
41
|
+
v-if="$slots.description"
|
|
42
|
+
#description
|
|
43
|
+
>
|
|
44
|
+
<slot name="description" />
|
|
45
|
+
</template>
|
|
46
|
+
|
|
47
|
+
<template
|
|
48
|
+
v-if="$slots.links || $slots.actions"
|
|
49
|
+
#links
|
|
50
|
+
>
|
|
51
|
+
<slot name="links">
|
|
52
|
+
<slot name="actions" />
|
|
53
|
+
</slot>
|
|
54
|
+
</template>
|
|
55
|
+
|
|
56
|
+
<slot />
|
|
57
|
+
</UPageHeader>
|
|
58
|
+
</template>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
title?: string;
|
|
3
|
+
description?: string;
|
|
4
|
+
back?: {
|
|
5
|
+
label: string;
|
|
6
|
+
to: string;
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
declare var __VLS_9: {}, __VLS_17: {}, __VLS_20: {}, __VLS_23: {}, __VLS_25: {}, __VLS_27: {};
|
|
10
|
+
type __VLS_Slots = {} & {
|
|
11
|
+
headline?: (props: typeof __VLS_9) => any;
|
|
12
|
+
} & {
|
|
13
|
+
title?: (props: typeof __VLS_17) => any;
|
|
14
|
+
} & {
|
|
15
|
+
description?: (props: typeof __VLS_20) => any;
|
|
16
|
+
} & {
|
|
17
|
+
links?: (props: typeof __VLS_23) => any;
|
|
18
|
+
} & {
|
|
19
|
+
actions?: (props: typeof __VLS_25) => any;
|
|
20
|
+
} & {
|
|
21
|
+
default?: (props: typeof __VLS_27) => any;
|
|
22
|
+
};
|
|
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
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
25
|
+
declare const _default: typeof __VLS_export;
|
|
26
|
+
export default _default;
|
|
27
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
28
|
+
new (): {
|
|
29
|
+
$slots: S;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
title?: string;
|
|
3
|
+
description?: string;
|
|
4
|
+
loading?: boolean;
|
|
5
|
+
};
|
|
6
|
+
declare var __VLS_1: {}, __VLS_3: {}, __VLS_15: {}, __VLS_17: {}, __VLS_19: {};
|
|
7
|
+
type __VLS_Slots = {} & {
|
|
8
|
+
header?: (props: typeof __VLS_1) => any;
|
|
9
|
+
} & {
|
|
10
|
+
actions?: (props: typeof __VLS_3) => any;
|
|
11
|
+
} & {
|
|
12
|
+
flush?: (props: typeof __VLS_15) => any;
|
|
13
|
+
} & {
|
|
14
|
+
default?: (props: typeof __VLS_17) => any;
|
|
15
|
+
} & {
|
|
16
|
+
footer?: (props: typeof __VLS_19) => any;
|
|
17
|
+
};
|
|
18
|
+
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>;
|
|
19
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
20
|
+
declare const _default: typeof __VLS_export;
|
|
21
|
+
export default _default;
|
|
22
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
23
|
+
new (): {
|
|
24
|
+
$slots: S;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
defineProps({
|
|
3
|
+
title: { type: String, required: false },
|
|
4
|
+
description: { type: String, required: false },
|
|
5
|
+
loading: { type: Boolean, required: false }
|
|
6
|
+
});
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<template>
|
|
10
|
+
<section class="border-default overflow-hidden rounded-lg border">
|
|
11
|
+
<div
|
|
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"
|
|
14
|
+
>
|
|
15
|
+
<slot name="header">
|
|
16
|
+
<div class="min-w-0">
|
|
17
|
+
<h2 class="text-sm font-semibold">
|
|
18
|
+
{{ title }}
|
|
19
|
+
</h2>
|
|
20
|
+
<p
|
|
21
|
+
v-if="description"
|
|
22
|
+
class="text-muted text-sm"
|
|
23
|
+
>
|
|
24
|
+
{{ description }}
|
|
25
|
+
</p>
|
|
26
|
+
</div>
|
|
27
|
+
</slot>
|
|
28
|
+
<div
|
|
29
|
+
v-if="$slots.actions"
|
|
30
|
+
class="flex items-center gap-2"
|
|
31
|
+
>
|
|
32
|
+
<slot name="actions" />
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
<div :class="$slots.flush || loading ? '' : 'p-4'">
|
|
36
|
+
<div
|
|
37
|
+
v-if="loading"
|
|
38
|
+
class="space-y-2 p-4"
|
|
39
|
+
>
|
|
40
|
+
<USkeleton class="h-5 w-1/2" />
|
|
41
|
+
<USkeleton class="h-5 w-1/3" />
|
|
42
|
+
</div>
|
|
43
|
+
<template v-else>
|
|
44
|
+
<slot name="flush" />
|
|
45
|
+
<slot />
|
|
46
|
+
</template>
|
|
47
|
+
</div>
|
|
48
|
+
<div
|
|
49
|
+
v-if="$slots.footer"
|
|
50
|
+
class="border-default border-t px-4 py-3"
|
|
51
|
+
>
|
|
52
|
+
<slot name="footer" />
|
|
53
|
+
</div>
|
|
54
|
+
</section>
|
|
55
|
+
</template>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
title?: string;
|
|
3
|
+
description?: string;
|
|
4
|
+
loading?: boolean;
|
|
5
|
+
};
|
|
6
|
+
declare var __VLS_1: {}, __VLS_3: {}, __VLS_15: {}, __VLS_17: {}, __VLS_19: {};
|
|
7
|
+
type __VLS_Slots = {} & {
|
|
8
|
+
header?: (props: typeof __VLS_1) => any;
|
|
9
|
+
} & {
|
|
10
|
+
actions?: (props: typeof __VLS_3) => any;
|
|
11
|
+
} & {
|
|
12
|
+
flush?: (props: typeof __VLS_15) => any;
|
|
13
|
+
} & {
|
|
14
|
+
default?: (props: typeof __VLS_17) => any;
|
|
15
|
+
} & {
|
|
16
|
+
footer?: (props: typeof __VLS_19) => any;
|
|
17
|
+
};
|
|
18
|
+
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>;
|
|
19
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
20
|
+
declare const _default: typeof __VLS_export;
|
|
21
|
+
export default _default;
|
|
22
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
23
|
+
new (): {
|
|
24
|
+
$slots: S;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
label: string;
|
|
3
|
+
loading?: boolean;
|
|
4
|
+
empty?: string;
|
|
5
|
+
};
|
|
6
|
+
declare var __VLS_11: {};
|
|
7
|
+
type __VLS_Slots = {} & {
|
|
8
|
+
default?: (props: typeof __VLS_11) => any;
|
|
9
|
+
};
|
|
10
|
+
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
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
12
|
+
declare const _default: typeof __VLS_export;
|
|
13
|
+
export default _default;
|
|
14
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
15
|
+
new (): {
|
|
16
|
+
$slots: S;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
defineProps({
|
|
3
|
+
label: { type: String, required: true },
|
|
4
|
+
loading: { type: Boolean, required: false },
|
|
5
|
+
empty: { type: String, required: false }
|
|
6
|
+
});
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<template>
|
|
10
|
+
<section class="border-default flex h-full flex-col gap-2 overflow-hidden rounded-lg border p-4">
|
|
11
|
+
<h2 class="text-muted text-xs font-medium tracking-wide uppercase">
|
|
12
|
+
{{ label }}
|
|
13
|
+
</h2>
|
|
14
|
+
<div
|
|
15
|
+
v-if="loading"
|
|
16
|
+
class="space-y-2"
|
|
17
|
+
>
|
|
18
|
+
<USkeleton class="h-5 w-2/3" />
|
|
19
|
+
<USkeleton class="h-4 w-1/3" />
|
|
20
|
+
</div>
|
|
21
|
+
<div
|
|
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>
|
|
34
|
+
</template>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
label: string;
|
|
3
|
+
loading?: boolean;
|
|
4
|
+
empty?: string;
|
|
5
|
+
};
|
|
6
|
+
declare var __VLS_11: {};
|
|
7
|
+
type __VLS_Slots = {} & {
|
|
8
|
+
default?: (props: typeof __VLS_11) => any;
|
|
9
|
+
};
|
|
10
|
+
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
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
12
|
+
declare const _default: typeof __VLS_export;
|
|
13
|
+
export default _default;
|
|
14
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
15
|
+
new (): {
|
|
16
|
+
$slots: S;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { TableColumn, TableRow } from '@nuxt/ui';
|
|
2
|
+
declare const __VLS_export: <T extends object>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_exposed?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
|
|
3
|
+
props: import("vue").PublicProps & __VLS_PrettifyLocal<{
|
|
4
|
+
data: T[];
|
|
5
|
+
columns: TableColumn<T>[];
|
|
6
|
+
loading?: boolean;
|
|
7
|
+
empty?: string;
|
|
8
|
+
} & {
|
|
9
|
+
onSelect?: ((e: Event, row: TableRow<T>) => any) | undefined;
|
|
10
|
+
}> & (typeof globalThis extends {
|
|
11
|
+
__VLS_PROPS_FALLBACK: infer P;
|
|
12
|
+
} ? P : {});
|
|
13
|
+
expose: (exposed: {}) => void;
|
|
14
|
+
attrs: any;
|
|
15
|
+
slots: {
|
|
16
|
+
[x: string]: ((props: any) => any) | undefined;
|
|
17
|
+
};
|
|
18
|
+
emit: (evt: "select", e: Event, row: TableRow<T>) => void;
|
|
19
|
+
}>) => import("vue").VNode & {
|
|
20
|
+
__ctx?: NonNullable<Awaited<typeof __VLS_setup>>;
|
|
21
|
+
};
|
|
22
|
+
declare const _default: typeof __VLS_export;
|
|
23
|
+
export default _default;
|
|
24
|
+
type __VLS_PrettifyLocal<T> = (T extends any ? {
|
|
25
|
+
[K in keyof T]: T[K];
|
|
26
|
+
} : {
|
|
27
|
+
[K in keyof T as K]: T[K];
|
|
28
|
+
}) & {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
defineProps({
|
|
3
|
+
data: { type: Array, required: true },
|
|
4
|
+
columns: { type: Array, required: true },
|
|
5
|
+
loading: { type: Boolean, required: false },
|
|
6
|
+
empty: { type: String, required: false }
|
|
7
|
+
});
|
|
8
|
+
const emit = defineEmits(["select"]);
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<template>
|
|
12
|
+
<div class="border-default overflow-hidden rounded-lg border">
|
|
13
|
+
<UTable
|
|
14
|
+
:data="data"
|
|
15
|
+
:columns="columns"
|
|
16
|
+
:loading="loading"
|
|
17
|
+
@select="(e, row) => emit('select', e, row)"
|
|
18
|
+
>
|
|
19
|
+
<template
|
|
20
|
+
v-for="(_, name) in $slots"
|
|
21
|
+
#[name]="slotProps"
|
|
22
|
+
>
|
|
23
|
+
<slot
|
|
24
|
+
:name="name"
|
|
25
|
+
v-bind="slotProps ?? {}"
|
|
26
|
+
/>
|
|
27
|
+
</template>
|
|
28
|
+
<template
|
|
29
|
+
v-if="empty && !$slots.empty"
|
|
30
|
+
#empty
|
|
31
|
+
>
|
|
32
|
+
<AdminEmpty
|
|
33
|
+
icon="i-lucide-inbox"
|
|
34
|
+
:title="empty"
|
|
35
|
+
/>
|
|
36
|
+
</template>
|
|
37
|
+
</UTable>
|
|
38
|
+
</div>
|
|
39
|
+
</template>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { TableColumn, TableRow } from '@nuxt/ui';
|
|
2
|
+
declare const __VLS_export: <T extends object>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_exposed?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
|
|
3
|
+
props: import("vue").PublicProps & __VLS_PrettifyLocal<{
|
|
4
|
+
data: T[];
|
|
5
|
+
columns: TableColumn<T>[];
|
|
6
|
+
loading?: boolean;
|
|
7
|
+
empty?: string;
|
|
8
|
+
} & {
|
|
9
|
+
onSelect?: ((e: Event, row: TableRow<T>) => any) | undefined;
|
|
10
|
+
}> & (typeof globalThis extends {
|
|
11
|
+
__VLS_PROPS_FALLBACK: infer P;
|
|
12
|
+
} ? P : {});
|
|
13
|
+
expose: (exposed: {}) => void;
|
|
14
|
+
attrs: any;
|
|
15
|
+
slots: {
|
|
16
|
+
[x: string]: ((props: any) => any) | undefined;
|
|
17
|
+
};
|
|
18
|
+
emit: (evt: "select", e: Event, row: TableRow<T>) => void;
|
|
19
|
+
}>) => import("vue").VNode & {
|
|
20
|
+
__ctx?: NonNullable<Awaited<typeof __VLS_setup>>;
|
|
21
|
+
};
|
|
22
|
+
declare const _default: typeof __VLS_export;
|
|
23
|
+
export default _default;
|
|
24
|
+
type __VLS_PrettifyLocal<T> = (T extends any ? {
|
|
25
|
+
[K in keyof T]: T[K];
|
|
26
|
+
} : {
|
|
27
|
+
[K in keyof T as K]: T[K];
|
|
28
|
+
}) & {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
declare var __VLS_1: {};
|
|
2
|
+
type __VLS_Slots = {} & {
|
|
3
|
+
default?: (props: typeof __VLS_1) => any;
|
|
4
|
+
};
|
|
5
|
+
declare const __VLS_base: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
6
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
7
|
+
declare const _default: typeof __VLS_export;
|
|
8
|
+
export default _default;
|
|
9
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
10
|
+
new (): {
|
|
11
|
+
$slots: S;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
declare var __VLS_1: {};
|
|
2
|
+
type __VLS_Slots = {} & {
|
|
3
|
+
default?: (props: typeof __VLS_1) => any;
|
|
4
|
+
};
|
|
5
|
+
declare const __VLS_base: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
6
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
7
|
+
declare const _default: typeof __VLS_export;
|
|
8
|
+
export default _default;
|
|
9
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
10
|
+
new (): {
|
|
11
|
+
$slots: S;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
declare var __VLS_84: {};
|
|
2
|
+
type __VLS_Slots = {} & {
|
|
3
|
+
default?: (props: typeof __VLS_84) => any;
|
|
4
|
+
};
|
|
5
|
+
declare const __VLS_base: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
6
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
7
|
+
declare const _default: typeof __VLS_export;
|
|
8
|
+
export default _default;
|
|
9
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
10
|
+
new (): {
|
|
11
|
+
$slots: S;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
defineOptions({ name: "AdminLayout" });
|
|
3
|
+
const t = useModuleT("admin");
|
|
4
|
+
const route = useRoute();
|
|
5
|
+
const config = useRuntimeConfig().public.admin;
|
|
6
|
+
const LanguageSelect = resolveComponent("LanguageSelect");
|
|
7
|
+
const AccountMenu = resolveComponent("AccountMenu");
|
|
8
|
+
const optional = (component) => typeof component === "string" ? void 0 : component;
|
|
9
|
+
const nuxtApp = useNuxtApp();
|
|
10
|
+
const multiLocale = (nuxtApp.$i18n?.locales?.value?.length ?? 0) > 1;
|
|
11
|
+
const { data: items } = await useMenu("admin.nav");
|
|
12
|
+
const home = config?.dashboard ?? config?.base ?? "/admin";
|
|
13
|
+
const stripLocale = (path) => {
|
|
14
|
+
const trimmed = path.length > 1 ? path.replace(/\/+$/, "") : path;
|
|
15
|
+
const first = trimmed.split("/")[1];
|
|
16
|
+
const codes = (nuxtApp.$i18n?.locales?.value ?? []).map((l) => typeof l === "string" ? l : l.code);
|
|
17
|
+
return first && codes.includes(first) ? trimmed.slice(first.length + 1) || "/" : trimmed;
|
|
18
|
+
};
|
|
19
|
+
const links = computed(() => (items.value ?? []).map((item) => ({
|
|
20
|
+
label: item.label,
|
|
21
|
+
icon: item.icon,
|
|
22
|
+
to: item.to,
|
|
23
|
+
external: item.external,
|
|
24
|
+
// The dashboard lives at the base itself, so it only matches exactly (ignoring a locale prefix).
|
|
25
|
+
active: !!item.to && (item.to === home ? stripLocale(route.path) === home : route.path === item.to || route.path.startsWith(`${item.to}/`))
|
|
26
|
+
})));
|
|
27
|
+
const title = computed(() => links.value.find((link) => link.active)?.label ?? t("title", "Administration"));
|
|
28
|
+
const sidebarOpen = ref(false);
|
|
29
|
+
watch(() => route.path, () => {
|
|
30
|
+
sidebarOpen.value = false;
|
|
31
|
+
});
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<template>
|
|
35
|
+
<div class="h-dvh w-full overflow-hidden bg-default">
|
|
36
|
+
<UDashboardGroup
|
|
37
|
+
unit="rem"
|
|
38
|
+
:ui="{ base: 'admin-group relative flex h-full w-full overflow-hidden' }"
|
|
39
|
+
>
|
|
40
|
+
<UDashboardSidebar
|
|
41
|
+
v-model:open="sidebarOpen"
|
|
42
|
+
:default-size="16"
|
|
43
|
+
:min-size="12"
|
|
44
|
+
:max-size="22"
|
|
45
|
+
:ui="{ root: 'min-h-0', footer: 'border-t border-default' }"
|
|
46
|
+
>
|
|
47
|
+
<template #header>
|
|
48
|
+
<NuxtLink
|
|
49
|
+
:to="home"
|
|
50
|
+
class="flex items-center gap-2 font-semibold"
|
|
51
|
+
>
|
|
52
|
+
<UIcon name="i-lucide-layout-dashboard" />
|
|
53
|
+
<span>{{ t("title", "Administration") }}</span>
|
|
54
|
+
</NuxtLink>
|
|
55
|
+
</template>
|
|
56
|
+
|
|
57
|
+
<UNavigationMenu
|
|
58
|
+
orientation="vertical"
|
|
59
|
+
:items="links"
|
|
60
|
+
:aria-label="t('nav.label', 'Admin navigation')"
|
|
61
|
+
/>
|
|
62
|
+
|
|
63
|
+
<template #footer>
|
|
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>
|
|
74
|
+
|
|
75
|
+
<UDashboardPanel :ui="{ body: 'p-0 sm:p-0 gap-0' }">
|
|
76
|
+
<template #header>
|
|
77
|
+
<UDashboardNavbar :title="title">
|
|
78
|
+
<template #leading>
|
|
79
|
+
<UDashboardSidebarCollapse />
|
|
80
|
+
</template>
|
|
81
|
+
<template #right>
|
|
82
|
+
<component
|
|
83
|
+
:is="optional(LanguageSelect)"
|
|
84
|
+
v-if="optional(LanguageSelect) && multiLocale"
|
|
85
|
+
/>
|
|
86
|
+
<UColorModeButton />
|
|
87
|
+
<component
|
|
88
|
+
:is="optional(AccountMenu)"
|
|
89
|
+
v-if="optional(AccountMenu)"
|
|
90
|
+
/>
|
|
91
|
+
</template>
|
|
92
|
+
</UDashboardNavbar>
|
|
93
|
+
</template>
|
|
94
|
+
|
|
95
|
+
<template #body>
|
|
96
|
+
<UPage :ui="{ root: 'block', center: 'admin-content px-4 sm:px-6' }">
|
|
97
|
+
<UPageBody class="mt-0 pt-6 pb-12 space-y-0">
|
|
98
|
+
<slot />
|
|
99
|
+
</UPageBody>
|
|
100
|
+
</UPage>
|
|
101
|
+
</template>
|
|
102
|
+
</UDashboardPanel>
|
|
103
|
+
</UDashboardGroup>
|
|
104
|
+
</div>
|
|
105
|
+
</template>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
declare var __VLS_84: {};
|
|
2
|
+
type __VLS_Slots = {} & {
|
|
3
|
+
default?: (props: typeof __VLS_84) => any;
|
|
4
|
+
};
|
|
5
|
+
declare const __VLS_base: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
6
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
7
|
+
declare const _default: typeof __VLS_export;
|
|
8
|
+
export default _default;
|
|
9
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
10
|
+
new (): {
|
|
11
|
+
$slots: S;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
defineOptions({ name: "AdminDashboardPage" });
|
|
3
|
+
const t = useModuleT("admin");
|
|
4
|
+
const { data: menu } = await useMenu("admin.nav");
|
|
5
|
+
const items = computed(() => (menu.value ?? []).filter((item) => item.id !== "admin-dashboard"));
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<template>
|
|
9
|
+
<AdminPage>
|
|
10
|
+
<AdminPageHeader
|
|
11
|
+
:title="t('dashboard.title', 'Dashboard')"
|
|
12
|
+
:description="t('dashboard.description', 'Overview and shortcuts for administrators.')"
|
|
13
|
+
/>
|
|
14
|
+
<WidgetArea name="admin.dashboard">
|
|
15
|
+
<template #empty>
|
|
16
|
+
<div
|
|
17
|
+
v-if="items.length"
|
|
18
|
+
class="grid grid-cols-[repeat(auto-fill,minmax(16rem,1fr))] gap-4"
|
|
19
|
+
>
|
|
20
|
+
<UPageCard
|
|
21
|
+
v-for="item in items"
|
|
22
|
+
:key="item.id"
|
|
23
|
+
:title="item.label"
|
|
24
|
+
:description="item.description"
|
|
25
|
+
:icon="item.icon"
|
|
26
|
+
:to="item.to"
|
|
27
|
+
/>
|
|
28
|
+
</div>
|
|
29
|
+
<p
|
|
30
|
+
v-else
|
|
31
|
+
class="text-muted"
|
|
32
|
+
>
|
|
33
|
+
{{ t("dashboard.empty", "Nothing here yet. Modules add their admin pages and widgets automatically.") }}
|
|
34
|
+
</p>
|
|
35
|
+
</template>
|
|
36
|
+
</WidgetArea>
|
|
37
|
+
</AdminPage>
|
|
38
|
+
</template>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"admin": {
|
|
3
|
+
"title": "Administration",
|
|
4
|
+
"nav": {
|
|
5
|
+
"label": "Admin-navigation"
|
|
6
|
+
},
|
|
7
|
+
"dashboard": {
|
|
8
|
+
"title": "Oversigt",
|
|
9
|
+
"description": "Overblik og genveje til administratorer.",
|
|
10
|
+
"empty": "Her er endnu intet. Moduler tilføjer deres admin-sider og widgets automatisk."
|
|
11
|
+
},
|
|
12
|
+
"backToSite": "Tilbage til siden",
|
|
13
|
+
"menu": {
|
|
14
|
+
"label": "Admin",
|
|
15
|
+
"description": "Administration"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"admin": {
|
|
3
|
+
"title": "Administration",
|
|
4
|
+
"nav": {
|
|
5
|
+
"label": "Admin navigation"
|
|
6
|
+
},
|
|
7
|
+
"dashboard": {
|
|
8
|
+
"title": "Dashboard",
|
|
9
|
+
"description": "Overview and shortcuts for administrators.",
|
|
10
|
+
"empty": "Nothing here yet. Modules add their admin pages and widgets automatically."
|
|
11
|
+
},
|
|
12
|
+
"backToSite": "Back to the site",
|
|
13
|
+
"menu": {
|
|
14
|
+
"label": "Admin",
|
|
15
|
+
"description": "Administration"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Nuxt } from '@nuxt/schema';
|
|
2
|
+
import type { MenuItem } from '@frigear-nu/menu/utils';
|
|
3
|
+
import type { WidgetDefinition } from '@frigear-nu/widget/utils';
|
|
4
|
+
/**
|
|
5
|
+
* The path of an admin page below the configured admin base (`admin.settings.base`, `/admin` by default):
|
|
6
|
+
* `adminRoute(nuxt, 'events', ':id')` is `/admin/events/:id`. Module defaults use it so they follow the host's base.
|
|
7
|
+
*/
|
|
8
|
+
export declare const adminRoute: (nuxt: Nuxt, ...segments: string[]) => string;
|
|
9
|
+
export declare const ADMIN_NAV_MENU = "admin.nav";
|
|
10
|
+
export declare const ADMIN_DASHBOARD_AREA = "admin.dashboard";
|
|
11
|
+
/** Adds entries to the admin sidebar (the `admin.nav` menu of `@frigear-nu/menu`). Call from a module's `setup()`. */
|
|
12
|
+
export declare const extendAdminNav: (nuxt: Nuxt, items: MenuItem | MenuItem[]) => void;
|
|
13
|
+
/** Adds widgets to the admin dashboard (the `admin.dashboard` area of `@frigear-nu/widget`). Call from a module's `setup()`. */
|
|
14
|
+
export declare const extendAdminDashboard: (nuxt: Nuxt, widgets: WidgetDefinition | WidgetDefinition[]) => void;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { extendMenu } from "@frigear-nu/menu/utils";
|
|
2
|
+
import { extendWidgets } from "@frigear-nu/widget/utils";
|
|
3
|
+
export const adminRoute = (nuxt, ...segments) => {
|
|
4
|
+
const base = nuxt.options.admin?.settings?.base ?? "/admin";
|
|
5
|
+
return [base.replace(/\/+$/, ""), ...segments].join("/");
|
|
6
|
+
};
|
|
7
|
+
export const ADMIN_NAV_MENU = "admin.nav";
|
|
8
|
+
export const ADMIN_DASHBOARD_AREA = "admin.dashboard";
|
|
9
|
+
export const extendAdminNav = (nuxt, items) => extendMenu(nuxt, ADMIN_NAV_MENU, items);
|
|
10
|
+
export const extendAdminDashboard = (nuxt, widgets) => extendWidgets(nuxt, ADMIN_DASHBOARD_AREA, widgets);
|
package/dist/types.d.mts
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,77 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frigear-nu/admin",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.0.1",
|
|
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
|
+
"homepage": "https://docs.frigear.dev/modules/admin",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/frigear-nu/frigear-nu.git",
|
|
9
|
+
"directory": "packages/admin"
|
|
10
|
+
},
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"type": "module",
|
|
13
|
+
"main": "./dist/module.mjs",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./dist/types.d.mts",
|
|
17
|
+
"import": "./dist/module.mjs",
|
|
18
|
+
"default": "./dist/module.mjs"
|
|
19
|
+
},
|
|
20
|
+
"./utils": {
|
|
21
|
+
"types": "./dist/runtime/utils/index.d.ts",
|
|
22
|
+
"import": "./dist/runtime/utils/index.js",
|
|
23
|
+
"default": "./dist/runtime/utils/index.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist"
|
|
28
|
+
],
|
|
29
|
+
"workspaces": [
|
|
30
|
+
"playground"
|
|
31
|
+
],
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@frigear-nu/menu": "0.0.8",
|
|
34
|
+
"@frigear-nu/module-kit": "0.1.6",
|
|
35
|
+
"@frigear-nu/widget": "0.0.0",
|
|
36
|
+
"@nuxt/kit": "^4.6.0",
|
|
37
|
+
"nuxt-auth-utils": "^0.5.30"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@nuxt/devtools": "^3.4.2",
|
|
41
|
+
"@nuxt/eslint-config": "^1.17.0",
|
|
42
|
+
"@nuxt/module-builder": "^1.0.3",
|
|
43
|
+
"@nuxt/schema": "^4.6.0",
|
|
44
|
+
"@nuxt/test-utils": "^4.3.2",
|
|
45
|
+
"@nuxt/ui": "^4.11.0",
|
|
46
|
+
"@types/node": "latest",
|
|
47
|
+
"changelogen": "^0.6.2",
|
|
48
|
+
"eslint": "^10.10.0",
|
|
49
|
+
"nuxt": "^4.6.1",
|
|
50
|
+
"typescript": "^6.0.3",
|
|
51
|
+
"vitest": "^5.0.1",
|
|
52
|
+
"vue-tsc": "^3.3.11"
|
|
53
|
+
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"@nuxt/ui": "^4.10.0"
|
|
56
|
+
},
|
|
57
|
+
"scripts": {
|
|
58
|
+
"postinstall": "nuxt-module-build build --stub && nuxt prepare",
|
|
59
|
+
"build": "nuxt-module-build build",
|
|
60
|
+
"dev": "npm run dev:prepare && nuxt dev playground",
|
|
61
|
+
"dev:build": "nuxt build playground",
|
|
62
|
+
"dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxt prepare playground",
|
|
63
|
+
"release": "npm run lint && npm run test && npm run prepack && changelogen --release && npm publish && git push --follow-tags",
|
|
64
|
+
"lint": "eslint .",
|
|
65
|
+
"lint:fix": "eslint . --fix",
|
|
66
|
+
"test": "vitest run",
|
|
67
|
+
"test:watch": "vitest watch",
|
|
68
|
+
"test:types": "vue-tsc --noEmit && cd playground && vue-tsc --noEmit"
|
|
69
|
+
},
|
|
70
|
+
"typesVersions": {
|
|
71
|
+
"*": {
|
|
72
|
+
".": [
|
|
73
|
+
"./dist/types.d.mts"
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
}
|
|
6
77
|
}
|