minor v1.4.0 #84

Merged
jkeffects merged 17 commits from develop into main 2025-04-16 15:01:08 +00:00
6 changed files with 198 additions and 152 deletions
Showing only changes of commit 1296331796 - Show all commits

10
package-lock.json generated
View file

@ -12,6 +12,7 @@
"@fullcalendar/core": "^6.1.15", "@fullcalendar/core": "^6.1.15",
"@fullcalendar/daygrid": "^6.1.15", "@fullcalendar/daygrid": "^6.1.15",
"@fullcalendar/interaction": "^6.1.15", "@fullcalendar/interaction": "^6.1.15",
"@fullcalendar/list": "^6.1.17",
"@fullcalendar/timegrid": "^6.1.15", "@fullcalendar/timegrid": "^6.1.15",
"@fullcalendar/vue3": "^6.1.15", "@fullcalendar/vue3": "^6.1.15",
"@headlessui/vue": "^1.7.23", "@headlessui/vue": "^1.7.23",
@ -2379,6 +2380,15 @@
"@fullcalendar/core": "~6.1.17" "@fullcalendar/core": "~6.1.17"
} }
}, },
"node_modules/@fullcalendar/list": {
"version": "6.1.17",
"resolved": "https://registry.npmjs.org/@fullcalendar/list/-/list-6.1.17.tgz",
"integrity": "sha512-fkyK49F9IxwlGUBVhJGsFpd/LTi/vRVERLIAe1HmBaGkjwpxnynm8TMLb9mZip97wvDk3CmZWduMe6PxscAlow==",
"license": "MIT",
"peerDependencies": {
"@fullcalendar/core": "~6.1.17"
}
},
"node_modules/@fullcalendar/timegrid": { "node_modules/@fullcalendar/timegrid": {
"version": "6.1.17", "version": "6.1.17",
"resolved": "https://registry.npmjs.org/@fullcalendar/timegrid/-/timegrid-6.1.17.tgz", "resolved": "https://registry.npmjs.org/@fullcalendar/timegrid/-/timegrid-6.1.17.tgz",

View file

@ -27,6 +27,7 @@
"@fullcalendar/core": "^6.1.15", "@fullcalendar/core": "^6.1.15",
"@fullcalendar/daygrid": "^6.1.15", "@fullcalendar/daygrid": "^6.1.15",
"@fullcalendar/interaction": "^6.1.15", "@fullcalendar/interaction": "^6.1.15",
"@fullcalendar/list": "^6.1.17",
"@fullcalendar/timegrid": "^6.1.15", "@fullcalendar/timegrid": "^6.1.15",
"@fullcalendar/vue3": "^6.1.15", "@fullcalendar/vue3": "^6.1.15",
"@headlessui/vue": "^1.7.23", "@headlessui/vue": "^1.7.23",

View file

@ -0,0 +1,170 @@
<template>
<div class="flex flex-col w-full h-full gap-2 justify-between overflow-hidden">
<div class="flex flex-row gap-2 max-xl:flex-wrap justify-between max-sm:justify-center">
<div class="flex flex-row max-xl:order-2">
<button
:primary="view == 'dayGridMonth'"
:primary-outline="view != 'dayGridMonth'"
class="rounded-r-none!"
@click="setView('dayGridMonth')"
>
Monat
</button>
<button
:primary="view == 'timeGridWeek'"
:primary-outline="view != 'timeGridWeek'"
class="rounded-none! border-x-0!"
@click="setView('timeGridWeek')"
>
Woche
</button>
<button
:primary="view == 'listMonth'"
:primary-outline="view != 'listMonth'"
class="rounded-l-none!"
@click="setView('listMonth')"
>
Liste
</button>
</div>
<p class="text-3xl w-full max-xl:order-1 text-center">{{ currentTitle }}</p>
<div class="flex flex-row max-xl:order-3">
<button primary-outline class="rounded-r-none!" @click="navigateView('prev')">
<ChevronLeftIcon />
</button>
<button
:primary="containsToday"
:primary-outline="!containsToday"
class="rounded-none! border-x-0!"
@click="
calendarApi?.today();
containsToday = true;
"
>
heute
</button>
<button primary-outline class="rounded-l-none!" @click="navigateView('next')">
<ChevronRightIcon />
</button>
</div>
</div>
<FullCalendar ref="fullCalendar" :options="calendarOptions" class="max-h-full h-full" />
</div>
</template>
<script setup lang="ts">
import { defineComponent, type PropType } from "vue";
import FullCalendar from "@fullcalendar/vue3";
import deLocale from "@fullcalendar/core/locales/de";
import dayGridPlugin from "@fullcalendar/daygrid";
import timeGridPlugin from "@fullcalendar/timegrid";
import listPlugin from "@fullcalendar/list";
import interactionPlugin from "@fullcalendar/interaction";
import { ChevronLeftIcon, ChevronRightIcon } from "@heroicons/vue/24/outline";
import type { CalendarOptions } from "@fullcalendar/core/index.js";
</script>
<script lang="ts">
export default defineComponent({
props: {
items: {
type: Array as PropType<
{
id: string;
title: string;
start: string;
end: string;
backgroundColor: string;
}[]
>,
default: [],
},
allowInteraction: {
type: Boolean,
default: true,
},
},
emits: {
dateSelect: ({ start, end, allDay }: { start: string; end: string; allDay: boolean }) => {
return typeof start == "string" && typeof end == "string" && typeof allDay === "boolean";
},
eventSelect: (id: string) => {
return typeof id == "string";
},
},
data() {
return {
view: "dayGridMonth" as "dayGridMonth" | "timeGridWeek" | "listMonth",
calendarApi: null as null | typeof FullCalendar,
currentTitle: "" as string,
containsToday: false as boolean,
};
},
computed: {
calendarOptions() {
return {
timeZone: "local",
locale: deLocale,
plugins: [dayGridPlugin, timeGridPlugin, listPlugin, interactionPlugin],
initialView: "dayGridMonth",
eventDisplay: "block",
headerToolbar: false,
weekends: true,
editable: this.allowInteraction,
selectable: this.allowInteraction,
selectMirror: false,
dayMaxEvents: true,
weekNumbers: true,
displayEventTime: true,
nowIndicator: true,
weekText: "KW",
allDaySlot: false,
events: this.items,
select: this.select,
eventClick: this.eventClick,
} as CalendarOptions;
},
},
mounted() {
this.calendarApi = (this.$refs.fullCalendar as typeof FullCalendar).getApi();
this.setTitle();
this.setContainsToday();
},
methods: {
setTitle() {
this.currentTitle = this.calendarApi?.view.title ?? "";
},
setView(view: "dayGridMonth" | "timeGridWeek" | "listMonth") {
this.calendarApi?.changeView(view);
this.view = view;
this.setTitle();
this.setContainsToday();
},
navigateView(change: "prev" | "next") {
if (change == "prev") {
this.calendarApi?.prev();
} else {
this.calendarApi?.next();
}
this.setTitle();
this.setContainsToday();
},
setContainsToday() {
const start = this.calendarApi?.view.currentStart;
const end = this.calendarApi?.view.currentEnd;
const today = new Date();
this.containsToday = today >= start && today < end;
},
select(e: any) {
this.$emit("dateSelect", {
start: e?.startStr ?? new Date().toISOString(),
end: e?.endStr ?? new Date().toISOString(),
allDay: e?.allDay ?? false,
});
},
eventClick(e: any) {
this.$emit("eventSelect", e.event.id);
},
},
});
</script>

View file

@ -72,7 +72,7 @@ a[button] {
button[primary]:not([primary="false"]), button[primary]:not([primary="false"]),
a[button][primary]:not([primary="false"]) { a[button][primary]:not([primary="false"]) {
@apply border border-transparent text-white bg-primary hover:bg-primary; @apply border-2 border-transparent text-white bg-primary hover:bg-primary;
} }
button[primary-outline]:not([primary-outline="false"]), button[primary-outline]:not([primary-outline="false"]),
@ -131,29 +131,3 @@ summary > svg {
summary::-webkit-details-marker { summary::-webkit-details-marker {
display: none; display: none;
} }
.fc-button-primary {
@apply bg-primary! border-primary! outline-hidden! ring-0! hover:bg-red-700! hover:border-red-700! h-10 text-center;
}
.fc-button-active {
@apply bg-red-500! border-red-500!;
}
.fc-toolbar {
@apply flex-wrap;
}
/* For screens between 850px and 768px */
@media (max-width: 850px) and (min-width: 768px) {
.fc-header-toolbar.fc-toolbar.fc-toolbar-ltr > .fc-toolbar-chunk:nth-child(2) {
@apply order-1!;
}
/* Your styles for this range */
}
/* For screens between 525px and 0px */
@media (max-width: 525px) and (min-width: 0px) {
/* Your styles for this range */
.fc-header-toolbar.fc-toolbar.fc-toolbar-ltr > .fc-toolbar-chunk:nth-child(2) {
@apply order-1!;
}
}

View file

@ -4,14 +4,17 @@
<div class="flex flex-row items-center justify-between pt-5 pb-3 px-7"> <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> <h1 class="font-bold text-xl h-8">Kalender</h1>
<div class="flex flex-row gap-2"> <div class="flex flex-row gap-2">
<PlusIcon class="text-gray-500 h-5 w-5 cursor-pointer" @click="select" /> <PlusIcon
class="text-gray-500 h-5 w-5 cursor-pointer"
@click="select({ start: '', end: '', allDay: false })"
/>
<LinkIcon class="text-gray-500 h-5 w-5 cursor-pointer" @click="openLinkModal" /> <LinkIcon class="text-gray-500 h-5 w-5 cursor-pointer" @click="openLinkModal" />
</div> </div>
</div> </div>
</template> </template>
<template #diffMain> <template #diffMain>
<div class="flex flex-col w-full h-full gap-2 justify-between px-7 overflow-hidden"> <div class="flex flex-col w-full h-full gap-2 justify-between px-7 overflow-hidden">
<FullCalendar :options="calendarOptions" class="max-h-full h-full" /> <CustomCalendar :items="formattedItems" @date-select="select" @event-select="eventClick" />
</div> </div>
</template> </template>
</MainTemplate> </MainTemplate>
@ -22,14 +25,10 @@ import { defineComponent, markRaw, defineAsyncComponent } from "vue";
import { mapActions, mapState } from "pinia"; import { mapActions, mapState } from "pinia";
import { useModalStore } from "@/stores/modal"; import { useModalStore } from "@/stores/modal";
import MainTemplate from "@/templates/Main.vue"; 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 { useCalendarStore } from "@/stores/admin/club/calendar"; import { useCalendarStore } from "@/stores/admin/club/calendar";
import { useAbilityStore } from "@/stores/ability"; import { useAbilityStore } from "@/stores/ability";
import { LinkIcon, PlusIcon } from "@heroicons/vue/24/outline"; import { LinkIcon, PlusIcon } from "@heroicons/vue/24/outline";
import CustomCalendar from "@/components/CustomCalendar.vue";
</script> </script>
<script lang="ts"> <script lang="ts">
@ -40,33 +39,6 @@ export default defineComponent({
computed: { computed: {
...mapState(useCalendarStore, ["formattedItems"]), ...mapState(useCalendarStore, ["formattedItems"]),
...mapState(useAbilityStore, ["can"]), ...mapState(useAbilityStore, ["can"]),
calendarOptions() {
return {
timeZone: "local",
locale: deLocale,
plugins: [dayGridPlugin, timeGridPlugin, interactionPlugin],
initialView: "dayGridMonth",
headerToolbar: {
left: "dayGridMonth,timeGridWeek",
center: "title",
right: "prev,today,next",
},
eventDisplay: "block",
weekends: true,
editable: true,
selectable: true,
selectMirror: false,
dayMaxEvents: true,
weekNumbers: true,
displayEventTime: true,
nowIndicator: true,
weekText: "KW",
allDaySlot: false,
events: this.formattedItems,
select: this.select,
eventClick: this.eventClick,
};
},
}, },
mounted() { mounted() {
this.fetchCalendars(); this.fetchCalendars();
@ -74,22 +46,22 @@ export default defineComponent({
methods: { methods: {
...mapActions(useModalStore, ["openModal"]), ...mapActions(useModalStore, ["openModal"]),
...mapActions(useCalendarStore, ["fetchCalendars"]), ...mapActions(useCalendarStore, ["fetchCalendars"]),
select(e: any) { select({ start, end, allDay }: { start: string; end: string; allDay: boolean }) {
if (!this.can("create", "club", "calendar")) return; if (!this.can("create", "club", "calendar")) return;
this.openModal( this.openModal(
markRaw(defineAsyncComponent(() => import("@/components/admin/club/calendar/CreateCalendarModal.vue"))), markRaw(defineAsyncComponent(() => import("@/components/admin/club/calendar/CreateCalendarModal.vue"))),
{ {
start: e?.startStr ?? new Date().toISOString(), start,
end: e?.endStr ?? new Date().toISOString(), end,
allDay: e?.allDay ?? false, allDay,
} }
); );
}, },
eventClick(e: any) { eventClick(id: string) {
if (!this.can("update", "club", "calendar")) return; if (!this.can("update", "club", "calendar")) return;
this.openModal( this.openModal(
markRaw(defineAsyncComponent(() => import("@/components/admin/club/calendar/UpdateCalendarModal.vue"))), markRaw(defineAsyncComponent(() => import("@/components/admin/club/calendar/UpdateCalendarModal.vue"))),
e.event.id id
); );
}, },
openLinkModal(e: any) { openLinkModal(e: any) {
@ -99,55 +71,4 @@ export default defineComponent({
}, },
}, },
}); });
/**
locale: deLocale,
events: this.absencesList.map((x) => ({
id: x.absenceId,
start: x.startDate,
end: x.endDate,
allday: true,
backgroundColor: this.getColorForAbsenceType(x.absenceType),
borderColor: '#ffffff',
title: this.getAbsenceType(x.absenceType) + ' ' + x.fullName,
})),
plugins: [
interactionPlugin,
dayGridPlugin,
timeGridPlugin,
listPlugin,
multiMonthPlugin,
],
initialView: 'dayGridMonth',
eventDisplay: 'block',
weekends: false,
editable: true,
selectable: true,
selectMirror: true,
dayMaxEvents: true,
weekNumbers: true,
displayEventTime: false,
weekText: 'KW',
validRange: { start: '2023-01-01', end: '' },
headerToolbar: {
left: 'today prev,next',
center: 'title',
right: 'listMonth,dayGridMonth,multiMonthYear,customview',
},
views: {
customview: {
type: 'multiMonth',
multiMonthMaxColumns: 1,
duration: { month: 12 },
buttonText: 'grid',
},
},
dateClick: this.handleDateSelect.bind(this),
datesSet: this.handleMonthChange.bind(this),
select: this.handleDateSelect.bind(this),
eventClick: this.handleEventClick.bind(this),
eventsSet: this.handleEvents.bind(this),
};
*/
</script> </script>

View file

@ -12,7 +12,7 @@
</template> </template>
<template #diffMain> <template #diffMain>
<div class="flex flex-col w-full h-full gap-2 justify-between px-7 overflow-hidden"> <div class="flex flex-col w-full h-full gap-2 justify-between px-7 overflow-hidden">
<FullCalendar :options="calendarOptions" class="max-h-full h-full" /> <CustomCalendar :items="formattedItems" :allow-interaction="false" @event-select="eventClick" />
</div> </div>
</template> </template>
</MainTemplate> </MainTemplate>
@ -23,14 +23,10 @@ import { defineComponent, markRaw, defineAsyncComponent } from "vue";
import { mapActions, mapState } from "pinia"; import { mapActions, mapState } from "pinia";
import { useModalStore } from "@/stores/modal"; import { useModalStore } from "@/stores/modal";
import MainTemplate from "@/templates/Main.vue"; 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 { InformationCircleIcon, LinkIcon } from "@heroicons/vue/24/outline";
import type { CalendarViewModel } from "@/viewmodels/admin/club/calendar.models"; import type { CalendarViewModel } from "@/viewmodels/admin/club/calendar.models";
import { RouterLink } from "vue-router"; import { RouterLink } from "vue-router";
import CustomCalendar from "@/components/CustomCalendar.vue";
</script> </script>
<script lang="ts"> <script lang="ts">
@ -50,32 +46,6 @@ export default defineComponent({
backgroundColor: c.type.color, backgroundColor: c.type.color,
})); }));
}, },
calendarOptions() {
return {
timeZone: "local",
locale: deLocale,
plugins: [dayGridPlugin, timeGridPlugin, interactionPlugin],
initialView: "dayGridMonth",
headerToolbar: {
left: "dayGridMonth,timeGridWeek",
center: "title",
right: "prev,today,next",
},
eventDisplay: "block",
weekends: true,
editable: false,
selectable: false,
selectMirror: false,
dayMaxEvents: true,
weekNumbers: true,
displayEventTime: true,
nowIndicator: true,
weekText: "KW",
allDaySlot: false,
events: this.formattedItems,
eventClick: this.eventClick,
};
},
}, },
mounted() { mounted() {
this.fetchCalendars(); this.fetchCalendars();
@ -93,10 +63,10 @@ export default defineComponent({
openLinkModal(e: any) { openLinkModal(e: any) {
this.openModal(markRaw(defineAsyncComponent(() => import("@/components/public/calendar/CalendarLinkModal.vue")))); this.openModal(markRaw(defineAsyncComponent(() => import("@/components/public/calendar/CalendarLinkModal.vue"))));
}, },
eventClick(e: any) { eventClick(id: string) {
this.openModal( this.openModal(
markRaw(defineAsyncComponent(() => import("@/components/public/calendar/ShowCalendarEntryModal.vue"))), markRaw(defineAsyncComponent(() => import("@/components/public/calendar/ShowCalendarEntryModal.vue"))),
this.calendars.find((c) => c.id == e.event.id) this.calendars.find((c) => c.id == id)
); );
}, },
}, },