@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.
Files changed (58) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +6 -4
  3. package/dist/runtime/app/components/EventAdminAddonDialog.d.vue.ts +2 -2
  4. package/dist/runtime/app/components/EventAdminAddonDialog.vue +3 -4
  5. package/dist/runtime/app/components/EventAdminAddonDialog.vue.d.ts +2 -2
  6. package/dist/runtime/app/components/EventAdminAddonGroupDialog.d.vue.ts +2 -2
  7. package/dist/runtime/app/components/EventAdminAddonGroupDialog.vue +3 -4
  8. package/dist/runtime/app/components/EventAdminAddonGroupDialog.vue.d.ts +2 -2
  9. package/dist/runtime/app/components/EventAdminAddonGroups.d.vue.ts +8 -8
  10. package/dist/runtime/app/components/EventAdminAddonGroups.vue +46 -55
  11. package/dist/runtime/app/components/EventAdminAddonGroups.vue.d.ts +8 -8
  12. package/dist/runtime/app/components/EventAdminTicketTypeDialog.d.vue.ts +2 -2
  13. package/dist/runtime/app/components/EventAdminTicketTypeDialog.vue +3 -4
  14. package/dist/runtime/app/components/EventAdminTicketTypeDialog.vue.d.ts +2 -2
  15. package/dist/runtime/app/components/EventCard.vue +7 -12
  16. package/dist/runtime/app/components/EventFormSlideover.d.vue.ts +13 -0
  17. package/dist/runtime/app/components/EventFormSlideover.vue +70 -0
  18. package/dist/runtime/app/components/EventFormSlideover.vue.d.ts +13 -0
  19. package/dist/runtime/app/components/EventTicketCard.vue +5 -7
  20. package/dist/runtime/app/components/EventTicketPicker.vue +5 -6
  21. package/dist/runtime/app/composables/index.d.ts +2 -0
  22. package/dist/runtime/app/composables/index.js +2 -0
  23. package/dist/runtime/app/composables/useEventActions.d.ts +26 -0
  24. package/dist/runtime/app/composables/useEventActions.js +61 -0
  25. package/dist/runtime/app/composables/useEventLinks.d.ts +12 -0
  26. package/dist/runtime/app/composables/useEventLinks.js +5 -0
  27. package/dist/runtime/app/composables/useEvents.d.ts +5 -2
  28. package/dist/runtime/app/pages/admin-event.vue +33 -21
  29. package/dist/runtime/app/pages/admin.vue +61 -135
  30. package/dist/runtime/app/pages/event.d.vue.ts +2 -2
  31. package/dist/runtime/app/pages/event.vue +113 -152
  32. package/dist/runtime/app/pages/event.vue.d.ts +2 -2
  33. package/dist/runtime/app/pages/events.vue +33 -31
  34. package/dist/runtime/app/pages/tickets.vue +21 -28
  35. package/dist/runtime/app/tabs/Attendees.d.vue.ts +2 -2
  36. package/dist/runtime/app/tabs/Attendees.vue +64 -60
  37. package/dist/runtime/app/tabs/Attendees.vue.d.ts +2 -2
  38. package/dist/runtime/app/tabs/Details.d.vue.ts +2 -2
  39. package/dist/runtime/app/tabs/Details.vue +4 -5
  40. package/dist/runtime/app/tabs/Details.vue.d.ts +2 -2
  41. package/dist/runtime/app/tabs/Tickets.d.vue.ts +2 -2
  42. package/dist/runtime/app/tabs/Tickets.vue +92 -99
  43. package/dist/runtime/app/tabs/Tickets.vue.d.ts +2 -2
  44. package/dist/runtime/app/utils/admin-event.d.ts +9 -12
  45. package/dist/runtime/app/utils/admin-event.js +0 -17
  46. package/dist/runtime/app/widgets/UpcomingEvents.vue +7 -9
  47. package/dist/runtime/app/widgets/UpcomingEventsStat.vue +5 -6
  48. package/dist/runtime/i18n/lang/da.json +111 -3
  49. package/dist/runtime/i18n/lang/en.json +111 -3
  50. package/dist/runtime/menus.js +2 -4
  51. package/dist/runtime/server/db/schema.postgresql.d.ts +1110 -0
  52. package/dist/runtime/server/db/schema.postgresql.js +125 -0
  53. package/dist/runtime/server/utils/admin.d.ts +5 -5
  54. package/dist/runtime/utils/index.d.ts +1 -0
  55. package/dist/runtime/utils/index.js +1 -0
  56. package/dist/runtime/utils/links.d.ts +21 -0
  57. package/dist/runtime/utils/links.js +12 -0
  58. package/package.json +21 -21
@@ -1,6 +1,6 @@
1
- import type { AdminEvent } from '../utils/admin-event.js';
1
+ import type { StaffEvent } from '../utils/admin-event.js';
2
2
  type __VLS_Props = {
3
- event: AdminEvent;
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 { type AdminEvent } from '../utils/admin-event.js';
1
+ import type { StaffEvent } from '../utils/admin-event.js';
2
2
  type __VLS_Props = {
3
- event: AdminEvent;
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 } = useAdminAction();
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
- <AdminSection :title="t('admin.details', 'Details')">
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
- </AdminSection>
72
+ </SectionCard>
74
73
  </template>
@@ -1,6 +1,6 @@
1
- import { type AdminEvent } from '../utils/admin-event.js';
1
+ import type { StaffEvent } from '../utils/admin-event.js';
2
2
  type __VLS_Props = {
3
- event: AdminEvent;
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 { type AdminEvent } from '../utils/admin-event.js';
1
+ import type { StaffEvent } from '../utils/admin-event.js';
2
2
  type __VLS_Props = {
3
- event: AdminEvent;
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 locale = useEventsLocale();
8
+ const format = useFormat();
11
9
  const tr = useEventsTranslate();
12
10
  const confirmAction = useConfirmDialog();
13
- const { run } = useAdminAction();
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
- if (!await confirmAction({ title: t("admin.confirmRemoveType", "Delete this ticket type?"), confirmText: t("admin.remove", "Delete"), cancelText: t("admin.cancel", "Cancel"), destructive: true })) return;
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-6">
43
- <AdminSection :title="t('admin.ticketTypes', 'Ticket types')">
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
- <template #flush>
55
- <UTable
56
- :data="event.ticketTypes"
57
- :columns="typeColumns"
58
- :ui="{ tr: 'data-[expanded=true]:bg-elevated/30' }"
59
- >
60
- <template #expand-cell="{ row }">
61
- <UButton
62
- color="neutral"
63
- variant="ghost"
64
- size="xs"
65
- :icon="row.getIsExpanded() ? 'i-lucide-chevron-down' : 'i-lucide-chevron-right'"
66
- :aria-label="t('admin.addonGroups', 'Add-ons')"
67
- @click="row.toggleExpanded()"
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
- </template>
70
- <template #name-cell="{ row }">
71
- <span class="text-highlighted font-medium">{{ tr(row.original.name) }}</span>
72
- </template>
73
- <template #price-cell="{ row }">
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
- </template>
132
- <template #empty>
133
- <AdminEmpty
134
- icon="i-lucide-ticket"
135
- :title="t('admin.noTypes', 'No ticket types yet: nobody can buy a ticket.')"
136
- >
137
- <UButton
138
- icon="i-lucide-plus"
139
- :label="t('admin.addType', 'Add ticket type')"
140
- @click="typeDialog?.open()"
141
- />
142
- </AdminEmpty>
143
- </template>
144
- </UTable>
145
- </template>
146
- </AdminSection>
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 { type AdminEvent } from '../utils/admin-event.js';
1
+ import type { StaffEvent } from '../utils/admin-event.js';
2
2
  type __VLS_Props = {
3
- event: AdminEvent;
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 AdminAddon = {
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 AdminAddonGroup = {
9
+ export type StaffAddonGroup = {
10
10
  id: string;
11
11
  title: Translated;
12
12
  required: boolean;
13
13
  multiple: boolean;
14
- addons: AdminAddon[];
14
+ addons: StaffAddon[];
15
15
  };
16
- export type AdminTicketType = {
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: AdminAddonGroup[];
28
+ addonGroups: StaffAddonGroup[];
29
29
  };
30
- export type AdminEvent = {
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: AdminTicketType[];
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 locale = useEventsLocale();
4
+ const format = useFormat();
5
5
  const tr = useEventsTranslate();
6
- const pages = useRuntimeConfig().public.events?.pages;
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
- <AdminListCard
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="pages?.admin"
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="eventTo(item.id)"
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
- {{ when(item.startsAt) }}<template v-if="item.location">
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
- </AdminListCard>
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 pages = useRuntimeConfig().public.events?.pages;
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
- <AdminStatCard
9
+ <StatCard
10
10
  :label="t('admin.upcoming.stat', 'Upcoming events')"
11
11
  icon="i-lucide-calendar-days"
12
- :to="pages?.admin"
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
  }