#15-messages #22
9 changed files with 139 additions and 44 deletions
|
@ -98,12 +98,12 @@ select[disabled] {
|
|||
|
||||
details {
|
||||
user-select: none;
|
||||
& summary svg {
|
||||
& summary svg[indicator] {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
details[open] {
|
||||
& summary svg {
|
||||
& summary svg[indicator] {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,16 @@ export const useNewsletterDatesStore = defineStore("newsletterDates", {
|
|||
},
|
||||
getters: {
|
||||
detectedChangeNewsletterDates: (state) =>
|
||||
!isEqual(state.origin, state.dates) && state.syncingNewsletterDates != "syncing",
|
||||
!isEqual(
|
||||
state.origin.sort(
|
||||
(a: NewsletterDatesViewModel, b: NewsletterDatesViewModel) =>
|
||||
new Date(a.calendar.starttime).getTime() - new Date(b.calendar.starttime).getTime()
|
||||
),
|
||||
state.dates.sort(
|
||||
(a: NewsletterDatesViewModel, b: NewsletterDatesViewModel) =>
|
||||
new Date(a.calendar.starttime).getTime() - new Date(b.calendar.starttime).getTime()
|
||||
)
|
||||
) && state.syncingNewsletterDates != "syncing",
|
||||
},
|
||||
actions: {
|
||||
setNewsletterDatesSyncingState(state: "synced" | "syncing" | "detectedChanges" | "failed") {
|
||||
|
@ -45,7 +54,7 @@ export const useNewsletterDatesStore = defineStore("newsletterDates", {
|
|||
|
||||
await http
|
||||
.patch(`/admin/newsletter/${newsletterId}/synchronize/dates`, {
|
||||
dates: differenceWith(this.dates, this.origin, isEqual),
|
||||
dates: this.dates,
|
||||
})
|
||||
.then((res) => {
|
||||
this.syncingNewsletterDates = "synced";
|
||||
|
|
|
@ -2,14 +2,14 @@ import type { CalendarViewModel } from "./calendar.models";
|
|||
|
||||
export interface NewsletterDatesViewModel {
|
||||
newsletterId: number;
|
||||
calendarId: number;
|
||||
calendarId: string;
|
||||
diffTitle: string | null;
|
||||
diffDescription: string | null;
|
||||
calendar: CalendarViewModel;
|
||||
}
|
||||
|
||||
export interface SyncNewsletterDatesViewModel {
|
||||
calendarId: number;
|
||||
calendarId: string;
|
||||
diffTitle?: string;
|
||||
diffDescription?: string;
|
||||
}
|
||||
|
|
|
@ -12,41 +12,89 @@
|
|||
>
|
||||
<summary class="flex flex-row gap-2 bg-primary p-2 w-full justify-between items-center cursor-pointer">
|
||||
<svg
|
||||
indicator
|
||||
class="fill-white stroke-white opacity-75 w-4 h-4"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path d="M12.95 10.707l.707-.707L8 4.343 6.586 5.757 10.828 10l-4.242 4.243L8 15.657l4.95-4.95z" />
|
||||
</svg>
|
||||
<p>{{ item.calendar.title }} {{ item.calendar.starttime }}</p>
|
||||
<p class="w-full text-white">
|
||||
{{ item.calendar.title }}:
|
||||
{{
|
||||
item.calendar.allDay
|
||||
? new Date(item.calendar.starttime ?? "").toLocaleDateString("de-DE", {
|
||||
day: "2-digit",
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
})
|
||||
: new Date(item.calendar.starttime ?? "").toLocaleDateString("de-DE", {
|
||||
day: "2-digit",
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
})
|
||||
}}
|
||||
</p>
|
||||
<TrashIcon
|
||||
v-if="can('create', 'club', 'newsletter')"
|
||||
class="w-5 h-5 p-1 box-content cursor-pointer text-white"
|
||||
@click.prevent="removeSelected(item.calendarId)"
|
||||
/>
|
||||
</summary>
|
||||
<input
|
||||
type="text"
|
||||
name="title"
|
||||
id="title"
|
||||
placeholder="Einscheidung"
|
||||
autocomplete="off"
|
||||
v-model="item.diffTitle"
|
||||
@keyup.prevent
|
||||
:disabled="!can('create', 'club', 'newsletter')"
|
||||
/>
|
||||
<QuillEditor
|
||||
id="top"
|
||||
theme="snow"
|
||||
placeholder="Entscheidung Inhalt..."
|
||||
style="height: 250px; max-height: 250px; min-height: 250px"
|
||||
contentType="html"
|
||||
:toolbar="toolbarOptions"
|
||||
v-model:content="item.diffDescription"
|
||||
:enable="can('create', 'club', 'newsletter')"
|
||||
:style="!can('create', 'club', 'newsletter') ? 'opacity: 75%; background: rgb(243 244 246)' : ''"
|
||||
/>
|
||||
<div class="flex flex-col gap-2 px-1">
|
||||
<input
|
||||
type="text"
|
||||
name="title"
|
||||
id="title"
|
||||
placeholder="alternativer Titel"
|
||||
autocomplete="off"
|
||||
v-model="item.diffTitle"
|
||||
@keyup.prevent
|
||||
:disabled="!can('create', 'club', 'newsletter')"
|
||||
/>
|
||||
<div>
|
||||
<QuillEditor
|
||||
id="top"
|
||||
theme="snow"
|
||||
placeholder="alternative Beschreibung..."
|
||||
style="height: 150px; max-height: 150px; min-height: 150px"
|
||||
contentType="html"
|
||||
:toolbar="toolbarOptions"
|
||||
v-model:content="item.diffDescription"
|
||||
:enable="can('create', 'club', 'newsletter')"
|
||||
:style="!can('create', 'club', 'newsletter') ? 'opacity: 75%; background: rgb(243 244 246)' : ''"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<button v-if="can('create', 'club', 'newsletter')" primary class="!w-fit" @click="addEntry">
|
||||
Eintrag hinzufügen
|
||||
</button>
|
||||
<form class="flex flex-row md:flex-row gap-2" @submit.prevent="addEntry">
|
||||
<select id="date" ref="date" value="" required>
|
||||
<option value="" disabled>Datum wählen</option>
|
||||
<option v-for="cal in filteredCalendar" :key="cal.id" :value="cal.id">
|
||||
{{ cal.title }}
|
||||
{{
|
||||
cal.allDay
|
||||
? new Date(cal.starttime).toLocaleDateString("de-DE", {
|
||||
day: "2-digit",
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
})
|
||||
: new Date(cal.starttime).toLocaleDateString("de-DE", {
|
||||
day: "2-digit",
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
})
|
||||
}}
|
||||
</option>
|
||||
</select>
|
||||
<button type="submit" primary class="!w-fit">hinzufügen</button>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -54,12 +102,16 @@
|
|||
import { defineComponent } from "vue";
|
||||
import { mapActions, mapState, mapWritableState } from "pinia";
|
||||
import Spinner from "@/components/Spinner.vue";
|
||||
import { useNewsletterStore } from "@/stores/admin/newsletter";
|
||||
import { QuillEditor } from "@vueup/vue-quill";
|
||||
import "@vueup/vue-quill/dist/vue-quill.snow.css";
|
||||
import { toolbarOptions } from "@/helpers/quillConfig";
|
||||
import { useNewsletterDatesStore } from "@/stores/admin/newsletterDates";
|
||||
import { useAbilityStore } from "@/stores/ability";
|
||||
import { useCalendarStore } from "@/stores/admin/calendar";
|
||||
import type { CalendarViewModel } from "@/viewmodels/admin/calendar.models";
|
||||
import { TrashIcon } from "@heroicons/vue/24/outline";
|
||||
import cloneDeep from "lodash.clonedeep";
|
||||
import type { NewsletterDatesViewModel } from "@/viewmodels/admin/newsletterDates.models";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
|
@ -69,16 +121,48 @@ export default defineComponent({
|
|||
},
|
||||
computed: {
|
||||
...mapWritableState(useNewsletterDatesStore, ["dates", "loading"]),
|
||||
...mapState(useCalendarStore, ["calendars"]),
|
||||
...mapState(useAbilityStore, ["can"]),
|
||||
filteredCalendar() {
|
||||
return this.calendars.filter((c) => !this.dates.map((d) => d.calendarId).includes(c.id));
|
||||
},
|
||||
sortedDates() {
|
||||
return this.dates.sort(
|
||||
(a: NewsletterDatesViewModel, b: NewsletterDatesViewModel) =>
|
||||
new Date(a.calendar.starttime).getTime() - new Date(b.calendar.starttime).getTime()
|
||||
);
|
||||
},
|
||||
calendarData() {
|
||||
return (dateId: string) => this.calendars.find((c) => c.id == dateId);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.fetchNewsletterDates();
|
||||
this.fetchCalendars();
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useNewsletterDatesStore, ["fetchNewsletterDates"]),
|
||||
addEntry(){
|
||||
// modal to select date
|
||||
}
|
||||
...mapActions(useCalendarStore, ["fetchCalendars"]),
|
||||
addEntry(e: any) {
|
||||
const formData = e.target.elements;
|
||||
const dateId = formData.date.value;
|
||||
|
||||
this.dates.push({
|
||||
newsletterId: parseInt(this.newsletterId ?? "0"),
|
||||
calendarId: dateId,
|
||||
diffTitle: "",
|
||||
diffDescription: "",
|
||||
calendar: cloneDeep(this.calendarData(dateId)) as CalendarViewModel,
|
||||
});
|
||||
|
||||
(this.$refs.date as HTMLSelectElement).value = "";
|
||||
},
|
||||
removeSelected(id: string) {
|
||||
let index = this.dates.findIndex((d) => d.calendarId == id);
|
||||
if (index != -1) {
|
||||
this.dates.splice(index, 1);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -72,11 +72,11 @@
|
|||
:key="member.id"
|
||||
class="flex flex-row h-fit w-full border border-primary rounded-md bg-primary p-2 text-white justify-between items-center"
|
||||
>
|
||||
<div>
|
||||
<p>{{ member.lastname }}, {{ member.firstname }} {{ member.nameaffix ? `- ${member.nameaffix}` : "" }}</p>
|
||||
<p>Newsletter senden an Typ: {{ member.sendNewsletter?.type.type }}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p>{{ member.lastname }}, {{ member.firstname }} {{ member.nameaffix ? `- ${member.nameaffix}` : "" }}</p>
|
||||
<p>Newsletter senden an Typ: {{ member.sendNewsletter?.type.type }}</p>
|
||||
</div>
|
||||
|
||||
<TrashIcon
|
||||
v-if="can('create', 'club', 'newsletter')"
|
||||
class="w-5 h-5 p-1 box-content cursor-pointer"
|
||||
|
@ -102,7 +102,6 @@ import {
|
|||
} from "@headlessui/vue";
|
||||
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/vue/20/solid";
|
||||
import { TrashIcon } from "@heroicons/vue/24/outline";
|
||||
import { useNewsletterStore } from "@/stores/admin/newsletter";
|
||||
import { useMemberStore } from "@/stores/admin/member";
|
||||
import type { MemberViewModel } from "@/viewmodels/admin/member.models";
|
||||
import { useNewsletterRecipientsStore } from "@/stores/admin/newsletterRecipients";
|
||||
|
|
|
@ -74,7 +74,7 @@ export default defineComponent({
|
|||
{ route: "admin-club-newsletter-overview", title: "Übersicht" },
|
||||
{ route: "admin-club-newsletter-dates", title: "Termine" },
|
||||
{ route: "admin-club-newsletter-recipients", title: "Empfänger" },
|
||||
{ route: "admin-club-newsletter-printout", title: "Druck" },
|
||||
{ route: "admin-club-newsletter-printout", title: "Druck/Versand" },
|
||||
],
|
||||
wantToClose: false as boolean,
|
||||
executeSyncAll: undefined as any,
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
>
|
||||
<summary class="flex flex-row gap-2 bg-primary p-2 w-full justify-between items-center cursor-pointer">
|
||||
<svg
|
||||
indicator
|
||||
class="fill-white stroke-white opacity-75 w-4 h-4"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
>
|
||||
<summary class="flex flex-row gap-2 bg-primary p-2 w-full justify-between items-center cursor-pointer">
|
||||
<svg
|
||||
indicator
|
||||
class="fill-white stroke-white opacity-75 w-4 h-4"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
|
@ -23,7 +24,7 @@
|
|||
type="text"
|
||||
name="title"
|
||||
id="title"
|
||||
placeholder="Einscheidung"
|
||||
placeholder="Entscheidung"
|
||||
autocomplete="off"
|
||||
v-model="item.topic"
|
||||
@keyup.prevent
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
>
|
||||
<summary class="flex flex-row gap-2 bg-primary p-2 w-full justify-between items-center cursor-pointer">
|
||||
<svg
|
||||
indicator
|
||||
class="fill-white stroke-white opacity-75 w-4 h-4"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
|
@ -23,7 +24,7 @@
|
|||
type="text"
|
||||
name="title"
|
||||
id="title"
|
||||
placeholder="Einscheidung"
|
||||
placeholder="Abstimmung"
|
||||
autocomplete="off"
|
||||
v-model="item.topic"
|
||||
@keyup.prevent
|
||||
|
|
Loading…
Reference in a new issue