@frigear-nu/events 0.0.9 → 0.1.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/module.json +1 -1
- package/dist/module.mjs +6 -4
- package/dist/runtime/app/components/EventAdminAddonDialog.d.vue.ts +2 -2
- package/dist/runtime/app/components/EventAdminAddonDialog.vue +3 -4
- package/dist/runtime/app/components/EventAdminAddonDialog.vue.d.ts +2 -2
- package/dist/runtime/app/components/EventAdminAddonGroupDialog.d.vue.ts +2 -2
- package/dist/runtime/app/components/EventAdminAddonGroupDialog.vue +3 -4
- package/dist/runtime/app/components/EventAdminAddonGroupDialog.vue.d.ts +2 -2
- package/dist/runtime/app/components/EventAdminAddonGroups.d.vue.ts +8 -8
- package/dist/runtime/app/components/EventAdminAddonGroups.vue +46 -55
- package/dist/runtime/app/components/EventAdminAddonGroups.vue.d.ts +8 -8
- package/dist/runtime/app/components/EventAdminTicketTypeDialog.d.vue.ts +2 -2
- package/dist/runtime/app/components/EventAdminTicketTypeDialog.vue +3 -4
- package/dist/runtime/app/components/EventAdminTicketTypeDialog.vue.d.ts +2 -2
- package/dist/runtime/app/components/EventCard.vue +7 -12
- package/dist/runtime/app/components/EventFormSlideover.d.vue.ts +13 -0
- package/dist/runtime/app/components/EventFormSlideover.vue +70 -0
- package/dist/runtime/app/components/EventFormSlideover.vue.d.ts +13 -0
- package/dist/runtime/app/components/EventTicketCard.vue +5 -7
- package/dist/runtime/app/components/EventTicketPicker.vue +5 -6
- package/dist/runtime/app/composables/index.d.ts +2 -0
- package/dist/runtime/app/composables/index.js +2 -0
- package/dist/runtime/app/composables/useEventActions.d.ts +26 -0
- package/dist/runtime/app/composables/useEventActions.js +61 -0
- package/dist/runtime/app/composables/useEventLinks.d.ts +12 -0
- package/dist/runtime/app/composables/useEventLinks.js +5 -0
- package/dist/runtime/app/composables/useEvents.d.ts +5 -2
- package/dist/runtime/app/pages/admin-event.vue +33 -21
- package/dist/runtime/app/pages/admin.vue +61 -135
- package/dist/runtime/app/pages/event.d.vue.ts +2 -2
- package/dist/runtime/app/pages/event.vue +113 -152
- package/dist/runtime/app/pages/event.vue.d.ts +2 -2
- package/dist/runtime/app/pages/events.vue +33 -31
- package/dist/runtime/app/pages/tickets.vue +21 -28
- package/dist/runtime/app/tabs/Attendees.d.vue.ts +2 -2
- package/dist/runtime/app/tabs/Attendees.vue +64 -60
- package/dist/runtime/app/tabs/Attendees.vue.d.ts +2 -2
- package/dist/runtime/app/tabs/Details.d.vue.ts +2 -2
- package/dist/runtime/app/tabs/Details.vue +4 -5
- package/dist/runtime/app/tabs/Details.vue.d.ts +2 -2
- package/dist/runtime/app/tabs/Tickets.d.vue.ts +2 -2
- package/dist/runtime/app/tabs/Tickets.vue +92 -99
- package/dist/runtime/app/tabs/Tickets.vue.d.ts +2 -2
- package/dist/runtime/app/utils/admin-event.d.ts +9 -12
- package/dist/runtime/app/utils/admin-event.js +0 -17
- package/dist/runtime/app/widgets/UpcomingEvents.vue +7 -9
- package/dist/runtime/app/widgets/UpcomingEventsStat.vue +5 -6
- package/dist/runtime/i18n/lang/da.json +111 -3
- package/dist/runtime/i18n/lang/en.json +111 -3
- package/dist/runtime/menus.js +2 -4
- package/dist/runtime/server/db/schema.postgresql.d.ts +1110 -0
- package/dist/runtime/server/db/schema.postgresql.js +125 -0
- package/dist/runtime/server/utils/admin.d.ts +5 -5
- package/dist/runtime/utils/index.d.ts +1 -0
- package/dist/runtime/utils/index.js +1 -0
- package/dist/runtime/utils/links.d.ts +21 -0
- package/dist/runtime/utils/links.js +12 -0
- package/package.json +21 -21
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { StaffEvent } from '../utils/admin-event.js';
|
|
2
2
|
type __VLS_Props = {
|
|
3
|
-
event:
|
|
3
|
+
event: StaffEvent;
|
|
4
4
|
};
|
|
5
5
|
declare const __VLS_export: 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>;
|
|
6
6
|
declare const _default: typeof __VLS_export;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { StaffEvent } from '../utils/admin-event.js';
|
|
2
2
|
type __VLS_Props = {
|
|
3
|
-
event:
|
|
3
|
+
event: StaffEvent;
|
|
4
4
|
};
|
|
5
5
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
6
6
|
refresh: () => any;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import { useAdminAction } from "../utils/admin-event";
|
|
3
2
|
defineOptions({ name: "EventsAdminDetailsTab" });
|
|
4
3
|
const props = defineProps({
|
|
5
4
|
event: { type: Object, required: true }
|
|
@@ -7,7 +6,7 @@ const props = defineProps({
|
|
|
7
6
|
const emit = defineEmits(["refresh"]);
|
|
8
7
|
const t = useModuleT("events");
|
|
9
8
|
const tr = useEventsTranslate();
|
|
10
|
-
const { busy, run } =
|
|
9
|
+
const { busy, run } = useAction();
|
|
11
10
|
const form = reactive({
|
|
12
11
|
name: void 0,
|
|
13
12
|
description: void 0,
|
|
@@ -24,11 +23,11 @@ const save = () => run(async () => {
|
|
|
24
23
|
body: { name: form.name, description: form.description ?? null, body: form.body ?? null }
|
|
25
24
|
});
|
|
26
25
|
emit("refresh");
|
|
27
|
-
}, t("admin.saved", "Saved."));
|
|
26
|
+
}, { success: t("admin.saved", "Saved.") });
|
|
28
27
|
</script>
|
|
29
28
|
|
|
30
29
|
<template>
|
|
31
|
-
<
|
|
30
|
+
<SectionCard :title="t('admin.details', 'Details')">
|
|
32
31
|
<form
|
|
33
32
|
id="admin-event-details"
|
|
34
33
|
class="flex flex-col gap-4"
|
|
@@ -70,5 +69,5 @@ const save = () => run(async () => {
|
|
|
70
69
|
/>
|
|
71
70
|
</div>
|
|
72
71
|
</template>
|
|
73
|
-
</
|
|
72
|
+
</SectionCard>
|
|
74
73
|
</template>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { StaffEvent } from '../utils/admin-event.js';
|
|
2
2
|
type __VLS_Props = {
|
|
3
|
-
event:
|
|
3
|
+
event: StaffEvent;
|
|
4
4
|
};
|
|
5
5
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
6
6
|
refresh: () => any;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { StaffEvent } from '../utils/admin-event.js';
|
|
2
2
|
type __VLS_Props = {
|
|
3
|
-
event:
|
|
3
|
+
event: StaffEvent;
|
|
4
4
|
};
|
|
5
5
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
6
6
|
refresh: () => any;
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import { formatMoney as formatPrice } from "@frigear-nu/ui/money";
|
|
3
|
-
import { useAdminAction } from "../utils/admin-event";
|
|
4
2
|
defineOptions({ name: "EventsAdminTicketsTab" });
|
|
5
3
|
const props = defineProps({
|
|
6
4
|
event: { type: Object, required: true }
|
|
7
5
|
});
|
|
8
6
|
const emit = defineEmits(["refresh"]);
|
|
9
7
|
const t = useModuleT("events");
|
|
10
|
-
const
|
|
8
|
+
const format = useFormat();
|
|
11
9
|
const tr = useEventsTranslate();
|
|
12
10
|
const confirmAction = useConfirmDialog();
|
|
13
|
-
const { run } =
|
|
11
|
+
const { run } = useAction();
|
|
14
12
|
const refresh = () => emit("refresh");
|
|
15
13
|
const typeColumns = computed(() => [
|
|
16
14
|
{ id: "expand" },
|
|
@@ -30,17 +28,32 @@ const typeDialog = useTemplateRef("typeDialog");
|
|
|
30
28
|
const groupDialog = useTemplateRef("groupDialog");
|
|
31
29
|
const addonDialog = useTemplateRef("addonDialog");
|
|
32
30
|
const removeType = async (ticketType) => {
|
|
33
|
-
|
|
31
|
+
const name = tr(ticketType.name);
|
|
32
|
+
if (!await confirmAction({
|
|
33
|
+
title: t("admin.confirmRemoveType", "Delete {name}?", { name }),
|
|
34
|
+
description: t("admin.confirmRemoveTypeDescription", "Its add-on groups and choices are deleted too. A ticket type with active tickets cannot be deleted."),
|
|
35
|
+
confirmText: t("admin.removeType", "Delete ticket type"),
|
|
36
|
+
destructive: true
|
|
37
|
+
})) return;
|
|
34
38
|
return run(async () => {
|
|
35
39
|
await $fetch(`/api/events/admin/ticket-types/${ticketType.id}`, { method: "DELETE" });
|
|
36
40
|
await refresh();
|
|
37
|
-
});
|
|
41
|
+
}, { success: t("admin.typeDeleted", "Deleted {name}", { name }) });
|
|
38
42
|
};
|
|
43
|
+
const menu = (ticketType) => [
|
|
44
|
+
[{ label: t("admin.editType", "Edit ticket type"), icon: "i-lucide-pencil", onSelect: () => typeDialog.value?.open(ticketType) }],
|
|
45
|
+
[{ label: t("admin.removeType", "Delete ticket type"), icon: "i-lucide-trash-2", color: "error", onSelect: () => {
|
|
46
|
+
void removeType(ticketType);
|
|
47
|
+
} }]
|
|
48
|
+
];
|
|
39
49
|
</script>
|
|
40
50
|
|
|
41
51
|
<template>
|
|
42
|
-
<div class="flex flex-col gap-
|
|
43
|
-
<
|
|
52
|
+
<div class="flex flex-col gap-4">
|
|
53
|
+
<SectionCard
|
|
54
|
+
:title="t('admin.ticketTypes', 'Ticket types')"
|
|
55
|
+
flush
|
|
56
|
+
>
|
|
44
57
|
<template #actions>
|
|
45
58
|
<UButton
|
|
46
59
|
icon="i-lucide-plus"
|
|
@@ -51,99 +64,79 @@ const removeType = async (ticketType) => {
|
|
|
51
64
|
@click="typeDialog?.open()"
|
|
52
65
|
/>
|
|
53
66
|
</template>
|
|
54
|
-
<
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
>
|
|
60
|
-
<
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
<UTable
|
|
68
|
+
:data="event.ticketTypes"
|
|
69
|
+
:columns="typeColumns"
|
|
70
|
+
:ui="tableUi({ tr: 'data-[expanded=true]:bg-elevated/30' })"
|
|
71
|
+
>
|
|
72
|
+
<template #expand-cell="{ row }">
|
|
73
|
+
<UButton
|
|
74
|
+
color="neutral"
|
|
75
|
+
variant="ghost"
|
|
76
|
+
size="xs"
|
|
77
|
+
:icon="row.getIsExpanded() ? 'i-lucide-chevron-down' : 'i-lucide-chevron-right'"
|
|
78
|
+
:aria-label="t('admin.addonGroups', 'Add-ons')"
|
|
79
|
+
@click="row.toggleExpanded()"
|
|
80
|
+
/>
|
|
81
|
+
</template>
|
|
82
|
+
<template #name-cell="{ row }">
|
|
83
|
+
<span class="text-highlighted font-medium">{{ tr(row.original.name) }}</span>
|
|
84
|
+
</template>
|
|
85
|
+
<template #price-cell="{ row }">
|
|
86
|
+
<span class="whitespace-nowrap">{{ format.money(row.original.price, row.original.currency) }}</span>
|
|
87
|
+
</template>
|
|
88
|
+
<template #capacity-cell="{ row }">
|
|
89
|
+
{{ row.original.capacity ?? "\u2013" }}
|
|
90
|
+
</template>
|
|
91
|
+
<template #access-cell="{ row }">
|
|
92
|
+
<div class="flex flex-wrap gap-1">
|
|
93
|
+
<UBadge
|
|
94
|
+
v-if="row.original.requiresMembership"
|
|
95
|
+
variant="subtle"
|
|
96
|
+
color="success"
|
|
97
|
+
:label="`${t('admin.requires', 'Requires')}: ${describeRule(row.original.requiresMembership)}`"
|
|
68
98
|
/>
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
<span class="whitespace-nowrap">{{ formatPrice(row.original.price, row.original.currency, locale) }}</span>
|
|
75
|
-
</template>
|
|
76
|
-
<template #capacity-cell="{ row }">
|
|
77
|
-
{{ row.original.capacity ?? "\u2013" }}
|
|
78
|
-
</template>
|
|
79
|
-
<template #access-cell="{ row }">
|
|
80
|
-
<div class="flex flex-wrap gap-1">
|
|
81
|
-
<UBadge
|
|
82
|
-
v-if="row.original.requiresMembership"
|
|
83
|
-
variant="subtle"
|
|
84
|
-
color="success"
|
|
85
|
-
:label="`${t('admin.requires', 'Requires')}: ${describeRule(row.original.requiresMembership)}`"
|
|
86
|
-
/>
|
|
87
|
-
<UBadge
|
|
88
|
-
v-if="row.original.excludesMembership"
|
|
89
|
-
variant="subtle"
|
|
90
|
-
color="warning"
|
|
91
|
-
:label="`${t('admin.notFor', 'Not for')}: ${describeRule(row.original.excludesMembership)}`"
|
|
92
|
-
/>
|
|
93
|
-
<span
|
|
94
|
-
v-if="!row.original.requiresMembership && !row.original.excludesMembership"
|
|
95
|
-
class="text-muted"
|
|
96
|
-
>–</span>
|
|
97
|
-
</div>
|
|
98
|
-
</template>
|
|
99
|
-
<template #addons-cell="{ row }">
|
|
100
|
-
{{ row.original.addonGroups.length }}
|
|
101
|
-
</template>
|
|
102
|
-
<template #actions-cell="{ row }">
|
|
103
|
-
<div class="flex justify-end gap-1">
|
|
104
|
-
<UButton
|
|
105
|
-
icon="i-lucide-pencil"
|
|
106
|
-
color="neutral"
|
|
107
|
-
variant="ghost"
|
|
108
|
-
size="xs"
|
|
109
|
-
:aria-label="t('admin.edit', 'Edit')"
|
|
110
|
-
@click="typeDialog?.open(row.original)"
|
|
111
|
-
/>
|
|
112
|
-
<UButton
|
|
113
|
-
icon="i-lucide-trash"
|
|
114
|
-
color="error"
|
|
115
|
-
variant="ghost"
|
|
116
|
-
size="xs"
|
|
117
|
-
:aria-label="t('admin.remove', 'Delete')"
|
|
118
|
-
@click="removeType(row.original)"
|
|
119
|
-
/>
|
|
120
|
-
</div>
|
|
121
|
-
</template>
|
|
122
|
-
<template #expanded="{ row }">
|
|
123
|
-
<EventAdminAddonGroups
|
|
124
|
-
:ticket-type="row.original"
|
|
125
|
-
@refresh="refresh"
|
|
126
|
-
@add-group="groupDialog?.open(row.original)"
|
|
127
|
-
@edit-group="(group) => groupDialog?.open(row.original, group)"
|
|
128
|
-
@add-addon="(group) => addonDialog?.open(group, row.original.currency)"
|
|
129
|
-
@edit-addon="(group, addon) => addonDialog?.open(group, row.original.currency, addon)"
|
|
99
|
+
<UBadge
|
|
100
|
+
v-if="row.original.excludesMembership"
|
|
101
|
+
variant="subtle"
|
|
102
|
+
color="warning"
|
|
103
|
+
:label="`${t('admin.notFor', 'Not for')}: ${describeRule(row.original.excludesMembership)}`"
|
|
130
104
|
/>
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
105
|
+
<span
|
|
106
|
+
v-if="!row.original.requiresMembership && !row.original.excludesMembership"
|
|
107
|
+
class="text-muted"
|
|
108
|
+
>–</span>
|
|
109
|
+
</div>
|
|
110
|
+
</template>
|
|
111
|
+
<template #addons-cell="{ row }">
|
|
112
|
+
{{ row.original.addonGroups.length }}
|
|
113
|
+
</template>
|
|
114
|
+
<template #actions-cell="{ row }">
|
|
115
|
+
<RowMenu
|
|
116
|
+
:items="menu(row.original)"
|
|
117
|
+
:subject="tr(row.original.name)"
|
|
118
|
+
/>
|
|
119
|
+
</template>
|
|
120
|
+
<template #expanded="{ row }">
|
|
121
|
+
<EventAdminAddonGroups
|
|
122
|
+
:ticket-type="row.original"
|
|
123
|
+
@refresh="refresh"
|
|
124
|
+
@add-group="groupDialog?.open(row.original)"
|
|
125
|
+
@edit-group="(group) => groupDialog?.open(row.original, group)"
|
|
126
|
+
@add-addon="(group) => addonDialog?.open(group, row.original.currency)"
|
|
127
|
+
@edit-addon="(group, addon) => addonDialog?.open(group, row.original.currency, addon)"
|
|
128
|
+
/>
|
|
129
|
+
</template>
|
|
130
|
+
<template #empty>
|
|
131
|
+
<UEmpty
|
|
132
|
+
variant="naked"
|
|
133
|
+
icon="i-lucide-ticket"
|
|
134
|
+
:title="t('admin.noTypes', 'No ticket types yet: nobody can buy a ticket.')"
|
|
135
|
+
:actions="[{ label: t('admin.addType', 'Add ticket type'), icon: 'i-lucide-plus', onClick: () => typeDialog?.open() }]"
|
|
136
|
+
/>
|
|
137
|
+
</template>
|
|
138
|
+
</UTable>
|
|
139
|
+
</SectionCard>
|
|
147
140
|
|
|
148
141
|
<EventAdminTicketTypeDialog
|
|
149
142
|
ref="typeDialog"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { StaffEvent } from '../utils/admin-event.js';
|
|
2
2
|
type __VLS_Props = {
|
|
3
|
-
event:
|
|
3
|
+
event: StaffEvent;
|
|
4
4
|
};
|
|
5
5
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
6
6
|
refresh: () => any;
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import type { Translated } from '../../../types.js';
|
|
2
|
-
export type
|
|
2
|
+
export type StaffAddon = {
|
|
3
3
|
id: string;
|
|
4
4
|
label: Translated;
|
|
5
5
|
description: Translated | null;
|
|
6
6
|
price: number;
|
|
7
7
|
capacity: number | null;
|
|
8
8
|
};
|
|
9
|
-
export type
|
|
9
|
+
export type StaffAddonGroup = {
|
|
10
10
|
id: string;
|
|
11
11
|
title: Translated;
|
|
12
12
|
required: boolean;
|
|
13
13
|
multiple: boolean;
|
|
14
|
-
addons:
|
|
14
|
+
addons: StaffAddon[];
|
|
15
15
|
};
|
|
16
|
-
export type
|
|
16
|
+
export type StaffTicketType = {
|
|
17
17
|
id: string;
|
|
18
18
|
key: string;
|
|
19
19
|
name: Translated;
|
|
@@ -25,9 +25,9 @@ export type AdminTicketType = {
|
|
|
25
25
|
saleEndsAt: string | null;
|
|
26
26
|
requiresMembership: 'any' | string[] | null;
|
|
27
27
|
excludesMembership: 'any' | string[] | null;
|
|
28
|
-
addonGroups:
|
|
28
|
+
addonGroups: StaffAddonGroup[];
|
|
29
29
|
};
|
|
30
|
-
export type
|
|
30
|
+
export type StaffEvent = {
|
|
31
31
|
id: string;
|
|
32
32
|
slug: string;
|
|
33
33
|
name: Translated;
|
|
@@ -37,12 +37,9 @@ export type AdminEvent = {
|
|
|
37
37
|
startsAt: string;
|
|
38
38
|
endsAt: string | null;
|
|
39
39
|
location: Translated | null;
|
|
40
|
-
ticketTypes:
|
|
40
|
+
ticketTypes: StaffTicketType[];
|
|
41
41
|
};
|
|
42
|
+
/** The fields the event list shows; the full `StaffEvent` fits it too. */
|
|
43
|
+
export type StaffEventSummary = Pick<StaffEvent, 'id' | 'slug' | 'name' | 'status' | 'startsAt' | 'location'>;
|
|
42
44
|
/** `datetime-local` inputs want the local time without a zone, not the stored ISO instant. */
|
|
43
45
|
export declare const toLocalInput: (value: string | null) => string;
|
|
44
|
-
/** Runs an admin action with a shared busy flag, toasting the success message or the server's error. */
|
|
45
|
-
export declare function useAdminAction(): {
|
|
46
|
-
busy: any;
|
|
47
|
-
run: (action: () => Promise<unknown>, success?: string) => Promise<void>;
|
|
48
|
-
};
|
|
@@ -3,20 +3,3 @@ export const toLocalInput = (value) => {
|
|
|
3
3
|
const date = new Date(value);
|
|
4
4
|
return new Date(date.getTime() - date.getTimezoneOffset() * 6e4).toISOString().slice(0, 16);
|
|
5
5
|
};
|
|
6
|
-
export function useAdminAction() {
|
|
7
|
-
const t = useModuleT("events");
|
|
8
|
-
const toast = useToast();
|
|
9
|
-
const busy = ref(false);
|
|
10
|
-
async function run(action, success) {
|
|
11
|
-
busy.value = true;
|
|
12
|
-
try {
|
|
13
|
-
await action();
|
|
14
|
-
if (success) toast.add({ title: success, color: "success" });
|
|
15
|
-
} catch (cause) {
|
|
16
|
-
toast.add({ title: cause.statusMessage ?? t("admin.error", "Something went wrong."), color: "error" });
|
|
17
|
-
} finally {
|
|
18
|
-
busy.value = false;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
return { busy, run };
|
|
22
|
-
}
|
|
@@ -1,21 +1,19 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
defineOptions({ name: "EventsUpcomingList" });
|
|
3
3
|
const t = useModuleT("events");
|
|
4
|
-
const
|
|
4
|
+
const format = useFormat();
|
|
5
5
|
const tr = useEventsTranslate();
|
|
6
|
-
const
|
|
6
|
+
const links = useEventLinks();
|
|
7
7
|
const { data, status } = useLazyFetch("/api/events/admin/upcoming", { key: "admin-events-upcoming" });
|
|
8
|
-
const eventTo = (id) => (pages?.adminEvent || "/admin/events/:id").replace(":id", encodeURIComponent(id));
|
|
9
|
-
const when = (value) => new Intl.DateTimeFormat(locale.value, { dateStyle: "medium", timeStyle: "short" }).format(new Date(value));
|
|
10
8
|
</script>
|
|
11
9
|
|
|
12
10
|
<template>
|
|
13
|
-
<
|
|
11
|
+
<ListCard
|
|
14
12
|
:title="t('admin.upcoming.title', 'Upcoming events')"
|
|
15
13
|
:loading="status === 'pending' && !data"
|
|
16
14
|
:is-empty="!data?.items.length"
|
|
17
15
|
:empty="t('admin.upcoming.empty', 'No upcoming events.')"
|
|
18
|
-
:to="
|
|
16
|
+
:to="links.admin()"
|
|
19
17
|
:to-label="t('admin.upcoming.all', 'All events')"
|
|
20
18
|
>
|
|
21
19
|
<li
|
|
@@ -24,16 +22,16 @@ const when = (value) => new Intl.DateTimeFormat(locale.value, { dateStyle: "medi
|
|
|
24
22
|
class="min-w-0"
|
|
25
23
|
>
|
|
26
24
|
<NuxtLink
|
|
27
|
-
:to="
|
|
25
|
+
:to="links.adminEvent(item.id)"
|
|
28
26
|
class="text-highlighted block truncate font-medium hover:underline"
|
|
29
27
|
>
|
|
30
28
|
{{ tr(item.name) }}
|
|
31
29
|
</NuxtLink>
|
|
32
30
|
<p class="text-muted truncate text-xs">
|
|
33
|
-
{{
|
|
31
|
+
{{ format.dateTime(item.startsAt) }}<template v-if="item.location">
|
|
34
32
|
· {{ tr(item.location) }}
|
|
35
33
|
</template>
|
|
36
34
|
</p>
|
|
37
35
|
</li>
|
|
38
|
-
</
|
|
36
|
+
</ListCard>
|
|
39
37
|
</template>
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
defineOptions({ name: "EventsUpcomingStat" });
|
|
3
3
|
const t = useModuleT("events");
|
|
4
|
-
const
|
|
4
|
+
const links = useEventLinks();
|
|
5
5
|
const { data, status } = useLazyFetch("/api/events/admin/upcoming", { key: "admin-events-upcoming" });
|
|
6
6
|
</script>
|
|
7
7
|
|
|
8
8
|
<template>
|
|
9
|
-
<
|
|
9
|
+
<StatCard
|
|
10
10
|
:label="t('admin.upcoming.stat', 'Upcoming events')"
|
|
11
11
|
icon="i-lucide-calendar-days"
|
|
12
|
-
:to="
|
|
12
|
+
:to="links.admin()"
|
|
13
|
+
:value="data?.total"
|
|
13
14
|
:loading="status === 'pending' && !data"
|
|
14
|
-
|
|
15
|
-
<span v-if="data">{{ data.total }}</span>
|
|
16
|
-
</AdminStatCard>
|
|
15
|
+
/>
|
|
17
16
|
</template>
|
|
@@ -59,8 +59,11 @@
|
|
|
59
59
|
"paid": "Betalt",
|
|
60
60
|
"cancelled": "Annulleret",
|
|
61
61
|
"pending": "Afventer",
|
|
62
|
-
"abandoned": "Opgivet"
|
|
63
|
-
|
|
62
|
+
"abandoned": "Opgivet",
|
|
63
|
+
"refunded": "Refunderet",
|
|
64
|
+
"expired": "Udløbet"
|
|
65
|
+
},
|
|
66
|
+
"qrAlt": "QR-kode til billetten"
|
|
64
67
|
},
|
|
65
68
|
"cart": {
|
|
66
69
|
"title": "Kurv",
|
|
@@ -120,7 +123,112 @@
|
|
|
120
123
|
"title": "Kommende arrangementer",
|
|
121
124
|
"empty": "Ingen kommende arrangementer.",
|
|
122
125
|
"all": "Alle arrangementer"
|
|
126
|
+
},
|
|
127
|
+
"saved": "Gemt.",
|
|
128
|
+
"details": "Detaljer",
|
|
129
|
+
"name": "Navn",
|
|
130
|
+
"description": "Beskrivelse",
|
|
131
|
+
"listDescription": "Arrangementer, deres billettyper og deltagere.",
|
|
132
|
+
"body": "Detaljer",
|
|
133
|
+
"bodyHelp": "Vises på arrangementssiden. Markdown understøttes.",
|
|
134
|
+
"save": "Gem",
|
|
135
|
+
"confirmRefund": "Refundér denne billet?",
|
|
136
|
+
"confirmCancel": "Annullér denne billet?",
|
|
137
|
+
"confirmRefundDescription": "Hele beløbet sendes tilbage til {name}, og billetten annulleres.",
|
|
138
|
+
"confirmCancelDescription": "Billetten til {name} virker ikke længere, og pladsen frigives.",
|
|
139
|
+
"refund": "Refundér billet",
|
|
140
|
+
"cancelTicket": "Annullér billet",
|
|
141
|
+
"refunded": "Refunderet.",
|
|
142
|
+
"cancelled": "Annulleret.",
|
|
143
|
+
"attendee": "Deltager",
|
|
144
|
+
"ticketType": "Billet",
|
|
145
|
+
"status": "Status",
|
|
146
|
+
"checkedIn": "Tjekket ind",
|
|
147
|
+
"attendees": "Deltagere",
|
|
148
|
+
"noAttendees": "Ingen billetter endnu.",
|
|
149
|
+
"price": "Pris",
|
|
150
|
+
"capacity": "Kapacitet",
|
|
151
|
+
"access": "Adgang",
|
|
152
|
+
"addonGroups": "Tilvalg",
|
|
153
|
+
"ruleAny": "Alle medlemskaber",
|
|
154
|
+
"confirmRemoveType": "Slet {name}?",
|
|
155
|
+
"confirmRemoveTypeDescription": "Dens tilvalgsgrupper og valg slettes også. En billettype med aktive billetter kan ikke slettes.",
|
|
156
|
+
"removeType": "Slet billettype",
|
|
157
|
+
"typeDeleted": "{name} er slettet",
|
|
158
|
+
"editType": "Redigér billettype",
|
|
159
|
+
"ticketTypes": "Billettyper",
|
|
160
|
+
"addType": "Tilføj billettype",
|
|
161
|
+
"requires": "Kræver",
|
|
162
|
+
"notFor": "Ikke for",
|
|
163
|
+
"noTypes": "Ingen billettyper endnu: ingen kan købe en billet.",
|
|
164
|
+
"ruleNone": "Ingen",
|
|
165
|
+
"rulePlans": "Bestemte medlemskaber",
|
|
166
|
+
"typeSaved": "Billettypen er gemt.",
|
|
167
|
+
"key": "Nøgle",
|
|
168
|
+
"keyHelp": "Et kort, unikt id, f.eks. medlem.",
|
|
169
|
+
"capacityHelp": "Lad stå tom for ubegrænset.",
|
|
170
|
+
"requiresMembership": "Kun for medlemmer af",
|
|
171
|
+
"requiresHelp": "Gæster uden et passende aktivt medlemskab kan ikke købe denne billet.",
|
|
172
|
+
"excludesMembership": "Ikke for medlemmer af",
|
|
173
|
+
"excludesHelp": "Medlemmer med disse medlemskaber kan ikke købe denne billet, så medlemmer og ikke-medlemmer kan have hver deres pris.",
|
|
174
|
+
"cancel": "Annullér",
|
|
175
|
+
"created": "Arrangementet er oprettet",
|
|
176
|
+
"create": "Nyt arrangement",
|
|
177
|
+
"createDescription": "Det gemmes som kladde, indtil du udgiver det.",
|
|
178
|
+
"createAction": "Opret kladde",
|
|
179
|
+
"slug": "URL-navn",
|
|
180
|
+
"slugHelp": "Små bogstaver, tal og bindestreger.",
|
|
181
|
+
"starts": "Starter",
|
|
182
|
+
"ends": "Slutter",
|
|
183
|
+
"location": "Sted",
|
|
184
|
+
"addonSaved": "Tilvalget er gemt.",
|
|
185
|
+
"editAddon": "Redigér valg",
|
|
186
|
+
"addAddon": "Tilføj valg",
|
|
187
|
+
"addonPriceHelp": "Lægges oveni billetprisen. 0 betyder inkluderet.",
|
|
188
|
+
"confirmRemoveGroup": "Slet {name}?",
|
|
189
|
+
"confirmRemoveGroupDescription": "Dens valg slettes også. Billetter, der allerede er købt, beholder de valg, de har truffet.",
|
|
190
|
+
"removeGroup": "Slet tilvalgsgruppe",
|
|
191
|
+
"groupDeleted": "{name} er slettet",
|
|
192
|
+
"confirmRemoveAddon": "Slet {name}?",
|
|
193
|
+
"confirmRemoveAddonDescription": "Billetter, der allerede er købt, beholder det valg, de har truffet.",
|
|
194
|
+
"removeAddon": "Slet valg",
|
|
195
|
+
"addonDeleted": "{name} er slettet",
|
|
196
|
+
"editGroup": "Redigér tilvalgsgruppe",
|
|
197
|
+
"groupRequired": "påkrævet",
|
|
198
|
+
"groupOptional": "valgfri",
|
|
199
|
+
"groupMultiple": "flere valg",
|
|
200
|
+
"groupSingle": "ét valg",
|
|
201
|
+
"included": "Inkluderet",
|
|
202
|
+
"noAddons": "Ingen valg endnu: købere har intet at vælge imellem.",
|
|
203
|
+
"addGroup": "Tilføj tilvalgsgruppe",
|
|
204
|
+
"groupSaved": "Tilvalgsgruppen er gemt.",
|
|
205
|
+
"groupTitle": "Titel",
|
|
206
|
+
"groupTitleHelp": "Vises over valgene, f.eks. Mad.",
|
|
207
|
+
"groupRequiredLabel": "Købere skal vælge mindst ét",
|
|
208
|
+
"groupMultipleLabel": "Købere kan vælge flere",
|
|
209
|
+
"title": "Arrangementer",
|
|
210
|
+
"viewPublic": "Se offentlig side",
|
|
211
|
+
"keyFacts": "Arrangement",
|
|
212
|
+
"saveEvent": "Gem arrangement",
|
|
213
|
+
"allEvents": "Alle arrangementer",
|
|
214
|
+
"empty": "Ingen arrangementer endnu",
|
|
215
|
+
"emptyDescription": "Opret det første arrangement for at begynde at sælge billetter.",
|
|
216
|
+
"duplicated": "Arrangementet er duplikeret som kladde",
|
|
217
|
+
"deleteTitle": "Slet {name}?",
|
|
218
|
+
"deleteDescription": "Dets billettyper og tilvalg slettes også. Arrangementer med betalte billetter kan ikke slettes.",
|
|
219
|
+
"deleteAction": "Slet arrangement",
|
|
220
|
+
"deleted": "{name} er slettet",
|
|
221
|
+
"deleteFailed": "Arrangementet kunne ikke slettes",
|
|
222
|
+
"open": "Åbn",
|
|
223
|
+
"clone": "Duplikér",
|
|
224
|
+
"statuses": {
|
|
225
|
+
"draft": "Kladde",
|
|
226
|
+
"published": "Udgivet",
|
|
227
|
+
"archived": "Arkiveret"
|
|
123
228
|
}
|
|
124
|
-
}
|
|
229
|
+
},
|
|
230
|
+
"listDescription": "Find et arrangement og få din billet.",
|
|
231
|
+
"noTicketsTitle": "Ingen billetter endnu",
|
|
232
|
+
"browseEvents": "Se arrangementer"
|
|
125
233
|
}
|
|
126
234
|
}
|