calendar link and typed link
This commit is contained in:
parent
e9a3e0c872
commit
bf3e0cd245
10 changed files with 236 additions and 5 deletions
|
@ -30,11 +30,15 @@ services:
|
|||
container_name: ff_member_administration_ui
|
||||
restart: unless-stopped
|
||||
|
||||
#environment:
|
||||
# - SERVER_ADRESS=<backend_host> # wichtig: ohne https:// bzw http://
|
||||
#volumes:
|
||||
# - <volume|local path>/favicon.png:/app/public/favicon.png
|
||||
# - <volume|local path>/favicon.png:/app/public/FFW-Logo.svg
|
||||
```
|
||||
|
||||
Wenn keine Server-Adresse angegeben wird, wird versucht das Backend unter der URL des Frontends zu erreichen. Dazu muss das Backend auf der gleichen URL wie das Frontend laufen. Zur Unterscheidung von Frontend und Backend bei gleicher URL müssen alle Anfragen mit dem PathPrefix `/api` an das Backend weitergeleitet werden.
|
||||
|
||||
Führen Sie dann den folgenden Befehl im Verzeichnis der compose-Datei aus, um den Container zu starten:
|
||||
|
||||
```sh
|
||||
|
|
138
src/components/admin/club/calendar/CalendarLinkModal.vue
Normal file
138
src/components/admin/club/calendar/CalendarLinkModal.vue
Normal file
|
@ -0,0 +1,138 @@
|
|||
<template>
|
||||
<div class="relative w-full md:max-w-md">
|
||||
<div class="flex flex-col items-center">
|
||||
<p class="text-xl font-medium">Kalenderlink</p>
|
||||
</div>
|
||||
<br />
|
||||
<div class="flex flex-col gap-2">
|
||||
<div>
|
||||
<Listbox v-model="selectedTypes" name="type" multiple>
|
||||
<ListboxLabel>Typen zur Anzeige auswählen</ListboxLabel>
|
||||
<div class="relative mt-1">
|
||||
<ListboxButton
|
||||
class="rounded-md shadow-sm relative block w-full px-3 py-2 border border-gray-300 focus:border-primary placeholder-gray-500 text-gray-900 rounded-b-md focus:outline-none focus:ring-0 focus:z-10 sm:text-sm resize-none"
|
||||
>
|
||||
<span class="block truncate w-full text-start">
|
||||
{{
|
||||
selectedTypes.length != 0
|
||||
? selectedTypes?.map((t) => t.type).join(", ")
|
||||
: "Standard-Typen werden ausgeliefert"
|
||||
}}
|
||||
</span>
|
||||
<span class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
||||
<ChevronUpDownIcon class="h-5 w-5 text-gray-400" aria-hidden="true" />
|
||||
</span>
|
||||
</ListboxButton>
|
||||
|
||||
<transition
|
||||
leave-active-class="transition duration-100 ease-in"
|
||||
leave-from-class="opacity-100"
|
||||
leave-to-class="opacity-0"
|
||||
>
|
||||
<ListboxOptions
|
||||
class="absolute mt-1 max-h-60 z-20 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black/5 focus:outline-none sm:text-sm h-32 overflow-y-auto"
|
||||
>
|
||||
<ListboxOption v-if="calendarTypes.length == 0" disabled as="template">
|
||||
<li :class="['relative cursor-default select-none py-2 pl-10 pr-4']">
|
||||
<span :class="['font-normal', 'block truncate']">keine Auswahl vorhanden</span>
|
||||
</li>
|
||||
</ListboxOption>
|
||||
<ListboxOption
|
||||
v-slot="{ active, selected }"
|
||||
v-for="type in calendarTypes"
|
||||
:key="type.id"
|
||||
:value="type"
|
||||
as="template"
|
||||
>
|
||||
<li
|
||||
:class="[
|
||||
active ? 'bg-red-200 text-amber-900' : 'text-gray-900',
|
||||
'relative cursor-default select-none py-2 pl-10 pr-4',
|
||||
]"
|
||||
>
|
||||
<span :class="[selected ? 'font-medium' : 'font-normal', 'block truncate']">
|
||||
{{ type.type }}
|
||||
<small v-if="type.passphrase">(passwortgeschützt)</small>
|
||||
<small v-if="type.nscdr">(standard-Auslieferung)</small>
|
||||
</span>
|
||||
<span v-if="selected" class="absolute inset-y-0 left-0 flex items-center pl-3 text-primary">
|
||||
<CheckIcon class="h-5 w-5" aria-hidden="true" />
|
||||
</span>
|
||||
</li>
|
||||
</ListboxOption>
|
||||
</ListboxOptions>
|
||||
</transition>
|
||||
</div>
|
||||
</Listbox>
|
||||
</div>
|
||||
<p class="flex flex-row text-sm">
|
||||
<InformationCircleIcon class="text-gray-500 h-5 w-5" /> Wenn kein Typ ausgewählt ist, werden die Standard-Typen
|
||||
zur Verfügung gestellt: <br />
|
||||
-> {{ defaultTypes }}
|
||||
</p>
|
||||
<br />
|
||||
<TextCopy :copyText="generatedLink" />
|
||||
<br />
|
||||
</div>
|
||||
|
||||
<RouterLink
|
||||
:to="{ name: 'public-calendar' }"
|
||||
title="Zur öffentlichen Kalender Anzeige"
|
||||
class="absolute top-3 right-3"
|
||||
target="_blank"
|
||||
>
|
||||
<CalendarDaysIcon class="text-gray-500 h-5 w-5" />
|
||||
</RouterLink>
|
||||
|
||||
<div class="flex flex-row justify-end">
|
||||
<div class="flex flex-row gap-4 py-2">
|
||||
<button primary-outline @click="closeModal">schließen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import { mapState, mapActions } from "pinia";
|
||||
import { RouterLink } from "vue-router";
|
||||
import { useModalStore } from "@/stores/modal";
|
||||
import { useCalendarTypeStore } from "@/stores/admin/calendarType";
|
||||
import type { CalendarTypeViewModel } from "@/viewmodels/admin/calendarType.models";
|
||||
import { Listbox, ListboxButton, ListboxOptions, ListboxOption, ListboxLabel } from "@headlessui/vue";
|
||||
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/vue/20/solid";
|
||||
import TextCopy from "@/components/TextCopy.vue";
|
||||
import { CalendarDaysIcon, InformationCircleIcon } from "@heroicons/vue/24/outline";
|
||||
import { host } from "@/serverCom";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
data() {
|
||||
return {
|
||||
selectedTypes: [] as Array<CalendarTypeViewModel>,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(useModalStore, ["data"]),
|
||||
...mapState(useCalendarTypeStore, ["calendarTypes"]),
|
||||
defaultTypes() {
|
||||
return this.calendarTypes
|
||||
.filter((t) => t.nscdr)
|
||||
.map((t) => t.type)
|
||||
.join(", ");
|
||||
},
|
||||
generatedLink() {
|
||||
let extend = this.selectedTypes.map((t) => [t.type, t.passphrase].filter((at) => at).join(":"));
|
||||
return `webcal://${host || window.location.host}/api/public/calendar${extend.length == 0 ? "" : "?types=" + extend.join("&types=")}`;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.fetchCalendarTypes();
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useModalStore, ["closeModal"]),
|
||||
...mapActions(useCalendarTypeStore, ["fetchCalendarTypes"]),
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -7,6 +7,7 @@
|
|||
</div>
|
||||
<p>{{ calendarType.type }}</p>
|
||||
<small v-if="calendarType.passphrase">(passwortgeschützt)</small>
|
||||
<small v-if="calendarType.nscdr">(standard-Auslieferung)</small>
|
||||
</div>
|
||||
<div class="flex flex-row">
|
||||
<RouterLink
|
||||
|
|
36
src/components/public/calendar/CalendarLinkModal.vue
Normal file
36
src/components/public/calendar/CalendarLinkModal.vue
Normal file
|
@ -0,0 +1,36 @@
|
|||
<template>
|
||||
<div class="w-full md:max-w-md">
|
||||
<div class="flex flex-col items-center">
|
||||
<p class="text-xl font-medium">Kalenderlink</p>
|
||||
</div>
|
||||
<br />
|
||||
<TextCopy :copyText="generatedLink" />
|
||||
|
||||
<div class="flex flex-row justify-end">
|
||||
<div class="flex flex-row gap-4 py-2">
|
||||
<button primary-outline @click="closeModal">schließen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import { mapState, mapActions } from "pinia";
|
||||
import { useModalStore } from "@/stores/modal";
|
||||
import TextCopy from "@/components/TextCopy.vue";
|
||||
import { host } from "@/serverCom";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
computed: {
|
||||
generatedLink() {
|
||||
return `webcal://${host || window.location.host}/api/public/calendar`;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useModalStore, ["closeModal"]),
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div class="w-full h-full flex flex-row gap-4">
|
||||
<div class="max-w-full flex grow gap-4 md:flex-row flex-col">
|
||||
<div class="max-w-full flex grow gap-4 flex-col">
|
||||
<slot name="main"></slot>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -540,6 +540,11 @@ const router = createRouter({
|
|||
name: "public-calendar",
|
||||
component: () => import("@/views/public/calendar/Calendar.vue"),
|
||||
},
|
||||
{
|
||||
path: "calendar-explain",
|
||||
name: "public-calendar-explain",
|
||||
component: () => import("@/views/public/calendar/CalendarExplain.vue"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
|
@ -4,8 +4,11 @@ import router from "./router";
|
|||
|
||||
let devMode = process.env.NODE_ENV === "development";
|
||||
|
||||
let host = devMode ? "localhost:5000" : process.env.SERVER_ADDRESS || "";
|
||||
let url = devMode ? "http://" + host : (host ? "https://" : "") + host;
|
||||
|
||||
const http = axios.create({
|
||||
baseURL: (devMode ? "http://localhost:5000" : "") + "/api",
|
||||
baseURL: url + "/api",
|
||||
headers: {
|
||||
"Cache-Control": "no-cache",
|
||||
Pragma: "no-cache",
|
||||
|
@ -81,4 +84,4 @@ export async function refreshToken(): Promise<void> {
|
|||
});
|
||||
}
|
||||
|
||||
export { http };
|
||||
export { http, host };
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
<template #topBar>
|
||||
<div class="flex flex-row items-center justify-between pt-5 pb-3 px-7">
|
||||
<h1 class="font-bold text-xl h-8">Kalender</h1>
|
||||
<LinkIcon class="text-gray-500 h-5 w-5 cursor-pointer" @click="openLinkModal" />
|
||||
</div>
|
||||
</template>
|
||||
<template #diffMain>
|
||||
|
@ -25,6 +26,7 @@ import timeGridPlugin from "@fullcalendar/timegrid";
|
|||
import interactionPlugin from "@fullcalendar/interaction";
|
||||
import { useCalendarStore } from "@/stores/admin/calendar";
|
||||
import { useAbilityStore } from "@/stores/ability";
|
||||
import { LinkIcon } from "@heroicons/vue/24/outline";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
|
@ -87,6 +89,11 @@ export default defineComponent({
|
|||
e.event.id
|
||||
);
|
||||
},
|
||||
openLinkModal(e: any) {
|
||||
this.openModal(
|
||||
markRaw(defineAsyncComponent(() => import("@/components/admin/club/calendar/CalendarLinkModal.vue")))
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
<template>
|
||||
<MainTemplate :showBack="false">
|
||||
<template #topBar>
|
||||
<div class="flex flex-row items-center justify-between pt-5 pb-3 px-7">
|
||||
<div class="flex flex-row items-center gap-4 pt-5 pb-3 px-7">
|
||||
<h1 class="font-bold text-xl h-8">Kalender</h1>
|
||||
<div class="grow"></div>
|
||||
<LinkIcon class="text-gray-500 h-5 w-5 cursor-pointer" @click="openLinkModal" />
|
||||
<RouterLink :to="{ name: 'public-calendar-explain' }">
|
||||
<InformationCircleIcon class="text-gray-500 h-5 w-5 cursor-pointer" />
|
||||
</RouterLink>
|
||||
</div>
|
||||
</template>
|
||||
<template #diffMain>
|
||||
|
@ -14,14 +19,18 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import { defineComponent, markRaw, defineAsyncComponent } from "vue";
|
||||
import { mapActions, mapState } from "pinia";
|
||||
import { useModalStore } from "@/stores/modal";
|
||||
import MainTemplate from "@/templates/Main.vue";
|
||||
import FullCalendar from "@fullcalendar/vue3";
|
||||
import deLocale from "@fullcalendar/core/locales/de";
|
||||
import dayGridPlugin from "@fullcalendar/daygrid";
|
||||
import timeGridPlugin from "@fullcalendar/timegrid";
|
||||
import interactionPlugin from "@fullcalendar/interaction";
|
||||
import { InformationCircleIcon, LinkIcon } from "@heroicons/vue/24/outline";
|
||||
import type { CalendarViewModel } from "@/viewmodels/admin/calendar.models";
|
||||
import { RouterLink } from "vue-router";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
|
@ -71,6 +80,7 @@ export default defineComponent({
|
|||
this.fetchCalendars();
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useModalStore, ["openModal"]),
|
||||
fetchCalendars() {
|
||||
this.$http
|
||||
.get("/public/calendar?output=json")
|
||||
|
@ -79,6 +89,9 @@ export default defineComponent({
|
|||
})
|
||||
.catch((err) => {});
|
||||
},
|
||||
openLinkModal(e: any) {
|
||||
this.openModal(markRaw(defineAsyncComponent(() => import("@/components/public/calendar/CalendarLinkModal.vue"))));
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
24
src/views/public/calendar/CalendarExplain.vue
Normal file
24
src/views/public/calendar/CalendarExplain.vue
Normal file
|
@ -0,0 +1,24 @@
|
|||
<template>
|
||||
<MainTemplate :showBack="false">
|
||||
<template #headerInsert>
|
||||
<RouterLink
|
||||
:to="{
|
||||
name: 'public-calendar',
|
||||
}"
|
||||
class="mid:hidden text-primary"
|
||||
>
|
||||
zum Kalender
|
||||
</RouterLink>
|
||||
</template>
|
||||
<template #topBar>
|
||||
<div class="flex flex-row items-center gap-2 pt-5 pb-3 px-7">
|
||||
<h1 class="font-bold text-xl h-8">Kalender-Erklärung</h1>
|
||||
</div>
|
||||
</template>
|
||||
<template #main> Kalender-Erklärung zur Nutzung </template>
|
||||
</MainTemplate>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import MainTemplate from "@/templates/Main.vue";
|
||||
</script>
|
Loading…
Reference in a new issue