newsletter recipients

This commit is contained in:
Julian Krauser 2024-12-27 13:25:37 +01:00
parent ec477b7d72
commit 81b7f533c7
6 changed files with 81 additions and 17 deletions

View file

@ -11,7 +11,7 @@ export function flattenQueryResult(result: Array<QueryResult>): Array<{ [key: st
for (const key in row) {
const value = row[key];
const newKey = prefix ? `${prefix}.${key}` : key;
const newKey = prefix ? `${prefix}_${key}` : key;
if (Array.isArray(value) && value.every((item) => typeof item === "object" && item !== null)) {
console.log(value, newKey);

View file

@ -2,7 +2,6 @@ import { defineStore } from "pinia";
import { http } from "@/serverCom";
import type { TableMeta } from "@/viewmodels/admin/query.models";
import type { DynamicQueryStructure, FieldType } from "@/types/dynamicQueries";
import { flattenQueryResult } from "@/helpers/queryFormatter";
export const useQueryBuilderStore = defineStore("queryBuilder", {
state: () => {
@ -31,23 +30,21 @@ export const useQueryBuilderStore = defineStore("queryBuilder", {
this.loading = "failed";
});
},
sendQuery(offset = 0, count = 25) {
sendQuery(offset = 0, count = 25, query?: DynamicQueryStructure | string) {
this.queryError = "";
this.data = [];
this.totalLength = 0;
if (this.query == undefined || this.query == "" || (typeof this.query != "string" && this.query.table == ""))
let queryToSend = query ?? this.query;
if (queryToSend == undefined || queryToSend == "" || (typeof queryToSend != "string" && queryToSend.table == ""))
return;
this.loadingData = "loading";
http
.post(`/admin/querybuilder/query?offset=${offset}&count=${count}`, {
query: this.query,
query: queryToSend,
})
.then((result) => {
if (result.data.stats == "success") {
this.data = flattenQueryResult(result.data.rows).map((row) => ({
id: row.id ?? "", // Ensure id is present
...row,
}));
this.data = result.data.rows;
this.totalLength = result.data.total;
this.loadingData = "fetched";
} else {

View file

@ -8,8 +8,8 @@ export interface NewsletterViewModel {
newsletterText: string;
newsletterSignatur: string;
isSent: boolean;
recipientsByQueryId?: number;
recipientsByQuery?: QueryViewModel;
recipientsByQueryId?: number | null;
recipientsByQuery?: QueryViewModel | null;
}
export interface CreateNewsletterViewModel {

View file

@ -15,7 +15,7 @@
<QuillEditor
id="summary"
theme="snow"
placeholder="Zusammenfassung zur Sitzung..."
placeholder="Zusammenfassung zum Newsletter..."
style="height: 250px; max-height: 250px; min-height: 250px"
contentType="html"
:toolbar="toolbarOptions"

View file

@ -5,11 +5,27 @@
&#8634; laden fehlgeschlagen
</p>
-- select members by query
<select v-model="recipientsByQueryId">
<option value="def">Optional</option>
<option v-for="query in queries" :key="query.id" :value="query.id">{{ query.title }}</option>
</select>
<p>Empfänger durch gespeicherte Abfrage</p>
<div class="flex flex-col gap-2 grow overflow-y-auto">
<div
v-for="member in queried"
: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>
</div>
<div class="w-full">
<Combobox v-model="recipients" :disabled="!can('create', 'club', 'newsletter')" multiple>
<ComboboxLabel>Empfänger suchen</ComboboxLabel>
<ComboboxLabel>weitere Empfänger suchen</ComboboxLabel>
<div class="relative mt-1">
<ComboboxInput
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"
@ -64,7 +80,6 @@
</div>
</Combobox>
</div>
<br />
<p>Ausgewählte Empfänger</p>
<div class="flex flex-col gap-2 grow overflow-y-auto">
<div
@ -104,8 +119,12 @@ import { CheckIcon, ChevronUpDownIcon } from "@heroicons/vue/20/solid";
import { TrashIcon } from "@heroicons/vue/24/outline";
import { useMemberStore } from "@/stores/admin/member";
import type { MemberViewModel } from "@/viewmodels/admin/member.models";
import { useNewsletterStore } from "@/stores/admin/newsletter";
import { useNewsletterRecipientsStore } from "@/stores/admin/newsletterRecipients";
import { useAbilityStore } from "@/stores/ability";
import { useQueryStoreStore } from "@/stores/admin/queryStore";
import { useQueryBuilderStore } from "@/stores/admin/queryBuilder";
import cloneDeep from "lodash.clonedeep";
</script>
<script lang="ts">
@ -113,6 +132,11 @@ export default defineComponent({
props: {
newsletterId: String,
},
watch: {
recipientsByQuery() {
this.loadQuery();
},
},
data() {
return {
query: "" as String,
@ -120,7 +144,10 @@ export default defineComponent({
},
computed: {
...mapWritableState(useNewsletterRecipientsStore, ["recipients", "loading"]),
...mapWritableState(useNewsletterStore, ["activeNewsletterObj"]),
...mapState(useMemberStore, ["members"]),
...mapState(useQueryStoreStore, ["queries"]),
...mapState(useQueryBuilderStore, ["data"]),
...mapState(useAbilityStore, ["can"]),
filtered(): Array<MemberViewModel> {
return this.query === ""
@ -144,20 +171,60 @@ export default defineComponent({
selected(): Array<MemberViewModel> {
return this.members.filter((m) => this.recipients.includes(m.id));
},
queried(): Array<MemberViewModel> {
let keys = Object.keys(this.data?.[0] ?? {});
let memberKey = keys.find((k) => k.includes("member_id"));
return this.members.filter((m) =>
this.data
.map((t) => ({
id: t.id,
...(memberKey ? { memberId: t[memberKey] } : {}),
}))
.some((d) => (d.memberId ?? d.id) == m.id)
);
},
recipientsByQueryId: {
get() {
return this.activeNewsletterObj?.recipientsByQueryId ?? "def";
},
set(val: string) {
if (this.activeNewsletterObj == undefined) return;
if (val == "def") {
this.activeNewsletterObj.recipientsByQueryId = null;
this.activeNewsletterObj.recipientsByQuery = null;
} else if (this.queries.find((q) => q.id == parseInt(val))) {
this.activeNewsletterObj.recipientsByQueryId = parseInt(val);
this.activeNewsletterObj.recipientsByQuery = cloneDeep(this.queries.find((q) => q.id == parseInt(val)));
this.sendQuery(0, 1000, this.recipientsByQuery?.query);
}
},
},
recipientsByQuery() {
return this.activeNewsletterObj?.recipientsByQuery;
},
},
mounted() {
this.fetchMembers();
this.fetchMembers(0, 1000, true);
this.fetchNewsletterRecipients();
this.fetchQueries();
this.loadQuery();
},
methods: {
...mapActions(useMemberStore, ["fetchMembers"]),
...mapActions(useNewsletterRecipientsStore, ["fetchNewsletterRecipients"]),
...mapActions(useQueryStoreStore, ["fetchQueries"]),
...mapActions(useQueryBuilderStore, ["sendQuery"]),
removeSelected(id: number) {
let index = this.recipients.findIndex((s) => s == id);
if (index != -1) {
this.recipients.splice(index, 1);
}
},
loadQuery() {
if (this.recipientsByQuery) {
this.sendQuery(0, 1000, this.recipientsByQuery.query);
}
},
},
});
</script>

View file

@ -141,7 +141,7 @@ export default defineComponent({
},
},
mounted() {
this.fetchMembers();
this.fetchMembers(0, 1000, true);
this.fetchProtocolPresence();
},
methods: {