Compare commits
No commits in common. "0e0f86adcec7412c5d0a79fe23ac36eed1e1236a" and "f3913a906c941bff7f84bf12f7d7743bc634a2e9" have entirely different histories.
0e0f86adce
...
f3913a906c
51 changed files with 60 additions and 1411 deletions
Binary file not shown.
Before Width: | Height: | Size: 34 KiB |
BIN
public/icon.png
BIN
public/icon.png
Binary file not shown.
Before Width: | Height: | Size: 29 KiB |
|
@ -12,7 +12,6 @@
|
||||||
<Teleport to="head">
|
<Teleport to="head">
|
||||||
<title>{{ clubName }}</title>
|
<title>{{ clubName }}</title>
|
||||||
<link rel="icon" type="image/ico" :href="config.server_address + '/api/public/favicon.ico'" />
|
<link rel="icon" type="image/ico" :href="config.server_address + '/api/public/favicon.ico'" />
|
||||||
<link rel="icon" type="image/png" href="/icon.png" />
|
|
||||||
<link rel="manifest" :href="config.server_address + '/api/public/manifest.webmanifest'" />
|
<link rel="manifest" :href="config.server_address + '/api/public/manifest.webmanifest'" />
|
||||||
</Teleport>
|
</Teleport>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,13 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<img v-if="useFallback" ref="fallback" src="/icon.png" alt="LOGO" class="h-full w-auto" />
|
<img ref="icon" :src="url + '/api/public/icon.png'" alt="LOGO" class="h-full w-auto" />
|
||||||
<img
|
|
||||||
v-else
|
|
||||||
ref="icon"
|
|
||||||
:src="url + '/api/public/icon.png'"
|
|
||||||
alt="LOGO"
|
|
||||||
class="h-full w-auto"
|
|
||||||
@error="useFallback = true"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
@ -24,11 +16,6 @@ export default defineComponent({
|
||||||
(this.$refs.icon as HTMLImageElement).src = url + "/api/public/icon.png?" + new Date().getTime();
|
(this.$refs.icon as HTMLImageElement).src = url + "/api/public/icon.png?" + new Date().getTime();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
useFallback: false,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(useSettingStore, ["readSetting"]),
|
...mapState(useSettingStore, ["readSetting"]),
|
||||||
icon() {
|
icon() {
|
||||||
|
|
|
@ -1,13 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<img v-if="useFallback" ref="fallback" src="/admin-logo.png" alt="LOGO" class="h-full w-auto" />
|
<img ref="logo" :src="url + '/api/public/applogo.png'" alt="LOGO" class="h-full w-auto" />
|
||||||
<img
|
|
||||||
v-else
|
|
||||||
ref="logo"
|
|
||||||
:src="url + '/api/public/applogo.png'"
|
|
||||||
alt="LOGO"
|
|
||||||
class="h-full w-auto"
|
|
||||||
@error="useFallback = true"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
@ -24,11 +16,6 @@ export default defineComponent({
|
||||||
(this.$refs.logo as HTMLImageElement).src = url + "/api/public/applogo.png?t=" + new Date().getTime();
|
(this.$refs.logo as HTMLImageElement).src = url + "/api/public/applogo.png?t=" + new Date().getTime();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
useFallback: false,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(useSettingStore, ["readSetting"]),
|
...mapState(useSettingStore, ["readSetting"]),
|
||||||
logo() {
|
logo() {
|
||||||
|
|
|
@ -1,64 +0,0 @@
|
||||||
<template>
|
|
||||||
<div
|
|
||||||
class="cursor-pointer"
|
|
||||||
:class="{ 'text-white': light, 'animate-pulse': isSensitive }"
|
|
||||||
:title="`2 mal klicken für ${action}`"
|
|
||||||
@click.prevent="handleClick"
|
|
||||||
>
|
|
||||||
<slot :isSensitive="isSensitive">
|
|
||||||
<CursorArrowRaysIcon v-if="!isSensitive" class="h-5 w-5" />
|
|
||||||
<CursorArrowRippleIcon v-else class="h-5 w-5" />
|
|
||||||
</slot>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { CursorArrowRaysIcon, CursorArrowRippleIcon } from "@heroicons/vue/24/outline";
|
|
||||||
import { defineComponent } from "vue";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
export default defineComponent({
|
|
||||||
props: {
|
|
||||||
light: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
type: String,
|
|
||||||
default: "Bestätigung",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
emits: ["click:first", "click:submit", "click:reset"],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
isSensitive: false as boolean,
|
|
||||||
timeout: undefined as any,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
beforeUnmount() {
|
|
||||||
try {
|
|
||||||
clearTimeout(this.timeout);
|
|
||||||
} catch (error) {}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
handleClick() {
|
|
||||||
if (this.isSensitive) {
|
|
||||||
clearTimeout(this.timeout);
|
|
||||||
this.isSensitive = false;
|
|
||||||
this.$emit("click:submit");
|
|
||||||
} else {
|
|
||||||
this.timeout = setTimeout(() => {
|
|
||||||
this.isSensitive = true;
|
|
||||||
this.$emit("click:first");
|
|
||||||
|
|
||||||
this.timeout = setTimeout(() => {
|
|
||||||
this.isSensitive = false;
|
|
||||||
this.$emit("click:reset");
|
|
||||||
}, 2000);
|
|
||||||
}, 500);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
|
@ -80,10 +80,6 @@
|
||||||
|
|
||||||
<input type="text" id="internalId" />
|
<input type="text" id="internalId" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<label for="note">Notiz (optional)</label>
|
|
||||||
<textarea type="text" id="note" />
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-row gap-2">
|
<div class="flex flex-row gap-2">
|
||||||
<button primary type="submit" :disabled="status == 'loading' || status?.status == 'success'">erstellen</button>
|
<button primary type="submit" :disabled="status == 'loading' || status?.status == 'success'">erstellen</button>
|
||||||
<Spinner v-if="status == 'loading'" class="my-auto" />
|
<Spinner v-if="status == 'loading'" class="my-auto" />
|
||||||
|
@ -158,7 +154,6 @@ export default defineComponent({
|
||||||
nameaffix: formData.nameaffix.value,
|
nameaffix: formData.nameaffix.value,
|
||||||
birthdate: formData.birthdate.value,
|
birthdate: formData.birthdate.value,
|
||||||
internalId: formData.internalId.value,
|
internalId: formData.internalId.value,
|
||||||
note: formData.note.value,
|
|
||||||
};
|
};
|
||||||
this.status = "loading";
|
this.status = "loading";
|
||||||
this.createMember(createMember)
|
this.createMember(createMember)
|
||||||
|
|
|
@ -1,164 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="w-full md:max-w-md">
|
|
||||||
<div class="flex flex-col items-center">
|
|
||||||
<p class="text-xl font-medium">Mitglied-Aus-/Fortbildung hinzufügen</p>
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
<form class="flex flex-col gap-4 py-2" @submit.prevent="triggerCreate">
|
|
||||||
<div>
|
|
||||||
<Listbox v-model="selectedEducation" name="education">
|
|
||||||
<ListboxLabel>Aus-/Fortbildung</ListboxLabel>
|
|
||||||
<div class="relative mt-1">
|
|
||||||
<ListboxButton
|
|
||||||
class="rounded-md shadow-xs 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-hidden focus:ring-0 focus:z-10 sm:text-sm resize-none"
|
|
||||||
>
|
|
||||||
<span class="block truncate w-full text-start">
|
|
||||||
{{
|
|
||||||
educations.length != 0
|
|
||||||
? (selectedEducation?.education ?? "bitte auswählen")
|
|
||||||
: "keine Auswahl vorhanden"
|
|
||||||
}}</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-hidden sm:text-sm h-32 overflow-y-auto"
|
|
||||||
>
|
|
||||||
<ListboxOption v-if="educations.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="education in educations"
|
|
||||||
:key="education.id"
|
|
||||||
:value="education"
|
|
||||||
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']">{{
|
|
||||||
education.education
|
|
||||||
}}</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>
|
|
||||||
<div>
|
|
||||||
<label for="start">Start</label>
|
|
||||||
<input type="date" id="start" required />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label for="end">Ende (optional)</label>
|
|
||||||
<input type="date" id="end" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label for="place">Ort (optional)</label>
|
|
||||||
<input type="text" id="place" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label for="note">Notiz (optional)</label>
|
|
||||||
<input type="text" id="note" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex flex-row gap-2">
|
|
||||||
<button primary type="submit" :disabled="status == 'loading' || status?.status == 'success'">erstellen</button>
|
|
||||||
<Spinner v-if="status == 'loading'" class="my-auto" />
|
|
||||||
<SuccessCheckmark v-else-if="status?.status == 'success'" />
|
|
||||||
<FailureXMark v-else-if="status?.status == 'failed'" />
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<div class="flex flex-row justify-end">
|
|
||||||
<div class="flex flex-row gap-4 py-2">
|
|
||||||
<button primary-outline @click="closeModal" :disabled="status == 'loading' || status?.status == 'success'">
|
|
||||||
abbrechen
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { defineComponent } from "vue";
|
|
||||||
import { mapState, mapActions } from "pinia";
|
|
||||||
import { useModalStore } from "@/stores/modal";
|
|
||||||
import Spinner from "@/components/Spinner.vue";
|
|
||||||
import SuccessCheckmark from "@/components/SuccessCheckmark.vue";
|
|
||||||
import FailureXMark from "@/components/FailureXMark.vue";
|
|
||||||
import { Listbox, ListboxButton, ListboxOptions, ListboxOption, ListboxLabel } from "@headlessui/vue";
|
|
||||||
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/vue/20/solid";
|
|
||||||
import { useEducationStore } from "@/stores/admin/configuration/education";
|
|
||||||
import type { EducationViewModel } from "@/viewmodels/admin/configuration/education.models";
|
|
||||||
import type { CreateMemberEducationViewModel } from "@/viewmodels/admin/club/member/memberEducation.models";
|
|
||||||
import { useMemberEducationStore } from "@/stores/admin/club/member/memberEducation";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
export default defineComponent({
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
status: null as null | "loading" | { status: "success" | "failed"; reason?: string },
|
|
||||||
timeout: undefined as any,
|
|
||||||
selectedEducation: undefined as undefined | EducationViewModel,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapState(useEducationStore, ["educations"]),
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.fetchEducations();
|
|
||||||
},
|
|
||||||
beforeUnmount() {
|
|
||||||
try {
|
|
||||||
clearTimeout(this.timeout);
|
|
||||||
} catch (error) {}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
...mapActions(useModalStore, ["closeModal"]),
|
|
||||||
...mapActions(useMemberEducationStore, ["createMemberEducation"]),
|
|
||||||
...mapActions(useEducationStore, ["fetchEducations"]),
|
|
||||||
triggerCreate(e: any) {
|
|
||||||
if (this.selectedEducation == undefined) return;
|
|
||||||
let formData = e.target.elements;
|
|
||||||
let createMemberEducation: CreateMemberEducationViewModel = {
|
|
||||||
start: formData.start.value,
|
|
||||||
end: formData.end.value,
|
|
||||||
note: formData.note.value,
|
|
||||||
place: formData.place.value,
|
|
||||||
educationId: this.selectedEducation.id,
|
|
||||||
};
|
|
||||||
this.status = "loading";
|
|
||||||
this.createMemberEducation(createMemberEducation)
|
|
||||||
.then(() => {
|
|
||||||
this.status = { status: "success" };
|
|
||||||
this.timeout = setTimeout(() => {
|
|
||||||
this.closeModal();
|
|
||||||
}, 1500);
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
this.status = { status: "failed" };
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
|
@ -1,82 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="w-full md:max-w-md">
|
|
||||||
<div class="flex flex-col items-center">
|
|
||||||
<p class="text-xl font-medium">Mitglied-Aus-/Fortbildung löschen</p>
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
<p class="text-center">Aus-/Fortbildung {{ memberEducation?.education }} löschen?</p>
|
|
||||||
<br />
|
|
||||||
|
|
||||||
<div class="flex flex-row gap-2">
|
|
||||||
<button
|
|
||||||
primary
|
|
||||||
type="submit"
|
|
||||||
:disabled="status == 'loading' || status?.status == 'success'"
|
|
||||||
@click="triggerDelete"
|
|
||||||
>
|
|
||||||
löschen
|
|
||||||
</button>
|
|
||||||
<Spinner v-if="status == 'loading'" class="my-auto" />
|
|
||||||
<SuccessCheckmark v-else-if="status?.status == 'success'" />
|
|
||||||
<FailureXMark v-else-if="status?.status == 'failed'" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex flex-row justify-end">
|
|
||||||
<div class="flex flex-row gap-4 py-2">
|
|
||||||
<button primary-outline @click="closeModal" :disabled="status == 'loading' || status?.status == 'success'">
|
|
||||||
abbrechen
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { defineComponent } from "vue";
|
|
||||||
import { mapState, mapActions } from "pinia";
|
|
||||||
import { useModalStore } from "@/stores/modal";
|
|
||||||
import Spinner from "@/components/Spinner.vue";
|
|
||||||
import SuccessCheckmark from "@/components/SuccessCheckmark.vue";
|
|
||||||
import FailureXMark from "@/components/FailureXMark.vue";
|
|
||||||
import { useMemberEducationStore } from "@/stores/admin/club/member/memberEducation";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
export default defineComponent({
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
status: null as null | "loading" | { status: "success" | "failed"; reason?: string },
|
|
||||||
timeout: undefined as any,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapState(useModalStore, ["data"]),
|
|
||||||
...mapState(useMemberEducationStore, ["memberEducations"]),
|
|
||||||
memberEducation() {
|
|
||||||
return this.memberEducations.find((m) => m.id == this.data);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
beforeUnmount() {
|
|
||||||
try {
|
|
||||||
clearTimeout(this.timeout);
|
|
||||||
} catch (error) {}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
...mapActions(useModalStore, ["closeModal"]),
|
|
||||||
...mapActions(useMemberEducationStore, ["deleteMemberEducation"]),
|
|
||||||
triggerDelete() {
|
|
||||||
this.status = "loading";
|
|
||||||
this.deleteMemberEducation(this.data)
|
|
||||||
.then(() => {
|
|
||||||
this.status = { status: "success" };
|
|
||||||
this.timeout = setTimeout(() => {
|
|
||||||
this.closeModal();
|
|
||||||
}, 1500);
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
this.status = { status: "failed" };
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
|
@ -1,198 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="w-full md:max-w-md">
|
|
||||||
<div class="flex flex-col items-center">
|
|
||||||
<p class="text-xl font-medium">Mitglied-Aus-/Fortbildung bearbeiten</p>
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
<Spinner v-if="loading == 'loading'" class="mx-auto" />
|
|
||||||
<p v-else-if="loading == 'failed'" @click="fetchItem" class="cursor-pointer">↺ laden fehlgeschlagen</p>
|
|
||||||
<form v-else-if="memberEducation != null" class="flex flex-col gap-4 py-2" @submit.prevent="triggerCreate">
|
|
||||||
<div>
|
|
||||||
<Listbox v-model="memberEducation.educationId" name="education">
|
|
||||||
<ListboxLabel>Aus-/Fortbildung</ListboxLabel>
|
|
||||||
<div class="relative mt-1">
|
|
||||||
<ListboxButton
|
|
||||||
class="rounded-md shadow-xs 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-hidden focus:ring-0 focus:z-10 sm:text-sm resize-none"
|
|
||||||
>
|
|
||||||
<span class="block truncate w-full text-start">
|
|
||||||
{{
|
|
||||||
educations.length != 0 ? (selectedEducation ?? "bitte auswählen") : "keine Auswahl vorhanden"
|
|
||||||
}}</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-hidden sm:text-sm h-32 overflow-y-auto"
|
|
||||||
>
|
|
||||||
<ListboxOption v-if="educations.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="education in educations"
|
|
||||||
:key="education.id"
|
|
||||||
:value="education.id"
|
|
||||||
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']">{{
|
|
||||||
education.education
|
|
||||||
}}</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>
|
|
||||||
<div>
|
|
||||||
<label for="start">Start</label>
|
|
||||||
<input type="date" id="start" required v-model="memberEducation.start" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label for="end">Ende (optional)</label>
|
|
||||||
<input type="date" id="end" v-model="memberEducation.end" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label for="place">Ort (optional)</label>
|
|
||||||
<input type="text" id="place" v-model="memberEducation.place" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label for="note">Notiz (optional)</label>
|
|
||||||
<input type="text" id="note" v-model="memberEducation.note" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex flex-row gap-2">
|
|
||||||
<button primary-outline type="reset" :disabled="canSaveOrReset" @click="resetForm">verwerfen</button>
|
|
||||||
<button primary type="submit" :disabled="status == 'loading' || canSaveOrReset">speichern</button>
|
|
||||||
<Spinner v-if="status == 'loading'" class="my-auto" />
|
|
||||||
<SuccessCheckmark v-else-if="status?.status == 'success'" />
|
|
||||||
<FailureXMark v-else-if="status?.status == 'failed'" />
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<div class="flex flex-row justify-end">
|
|
||||||
<div class="flex flex-row gap-4 py-2">
|
|
||||||
<button primary-outline @click="closeModal" :disabled="status == 'loading' || status?.status == 'success'">
|
|
||||||
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 Spinner from "@/components/Spinner.vue";
|
|
||||||
import SuccessCheckmark from "@/components/SuccessCheckmark.vue";
|
|
||||||
import FailureXMark from "@/components/FailureXMark.vue";
|
|
||||||
import { Listbox, ListboxButton, ListboxOptions, ListboxOption, ListboxLabel } from "@headlessui/vue";
|
|
||||||
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/vue/20/solid";
|
|
||||||
import { useEducationStore } from "@/stores/admin/configuration/education";
|
|
||||||
import type {
|
|
||||||
CreateMemberEducationViewModel,
|
|
||||||
MemberEducationViewModel,
|
|
||||||
UpdateMemberEducationViewModel,
|
|
||||||
} from "@/viewmodels/admin/club/member/memberEducation.models";
|
|
||||||
import { useMemberEducationStore } from "@/stores/admin/club/member/memberEducation";
|
|
||||||
import isEqual from "lodash.isequal";
|
|
||||||
import cloneDeep from "lodash.clonedeep";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
export default defineComponent({
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
loading: "loading" as "loading" | "fetched" | "failed",
|
|
||||||
status: null as null | "loading" | { status: "success" | "failed"; reason?: string },
|
|
||||||
origin: null as null | MemberEducationViewModel,
|
|
||||||
memberEducation: null as null | MemberEducationViewModel,
|
|
||||||
timeout: undefined as any,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapState(useEducationStore, ["educations"]),
|
|
||||||
...mapState(useModalStore, ["data"]),
|
|
||||||
canSaveOrReset(): boolean {
|
|
||||||
return isEqual(this.origin, this.memberEducation);
|
|
||||||
},
|
|
||||||
selectedEducation() {
|
|
||||||
return this.educations.find((ms) => ms.id == this.memberEducation?.educationId)?.education;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.fetchEducations();
|
|
||||||
this.fetchItem();
|
|
||||||
},
|
|
||||||
beforeUnmount() {
|
|
||||||
try {
|
|
||||||
clearTimeout(this.timeout);
|
|
||||||
} catch (error) {}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
...mapActions(useModalStore, ["closeModal"]),
|
|
||||||
...mapActions(useMemberEducationStore, ["updateMemberEducation", "fetchMemberEducationById"]),
|
|
||||||
...mapActions(useEducationStore, ["fetchEducations"]),
|
|
||||||
resetForm() {
|
|
||||||
this.memberEducation = cloneDeep(this.origin);
|
|
||||||
},
|
|
||||||
fetchItem() {
|
|
||||||
this.fetchMemberEducationById(this.data)
|
|
||||||
.then((result) => {
|
|
||||||
this.memberEducation = result.data;
|
|
||||||
this.origin = cloneDeep(result.data);
|
|
||||||
this.loading = "fetched";
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
this.loading = "failed";
|
|
||||||
});
|
|
||||||
},
|
|
||||||
triggerCreate(e: any) {
|
|
||||||
if (this.memberEducation == null) return;
|
|
||||||
let formData = e.target.elements;
|
|
||||||
let updateMemberEducation: UpdateMemberEducationViewModel = {
|
|
||||||
id: this.memberEducation.id,
|
|
||||||
start: formData.start.value,
|
|
||||||
end: formData.end.value,
|
|
||||||
note: formData.note.value,
|
|
||||||
place: formData.place.value,
|
|
||||||
educationId: this.memberEducation.educationId,
|
|
||||||
};
|
|
||||||
this.status = "loading";
|
|
||||||
this.updateMemberEducation(updateMemberEducation)
|
|
||||||
.then(() => {
|
|
||||||
this.fetchItem();
|
|
||||||
this.status = { status: "success" };
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
this.status = { status: "failed" };
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
this.timeout = setTimeout(() => {
|
|
||||||
this.status = null;
|
|
||||||
}, 2000);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
|
@ -1,54 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="flex flex-col h-fit w-full border border-primary rounded-md">
|
|
||||||
<div class="bg-primary p-2 text-white flex flex-row gap-2 justify-between items-center">
|
|
||||||
<p class="grow">{{ education.education }}</p>
|
|
||||||
<PencilIcon v-if="can('update', 'club', 'member')" class="w-5 h-5 cursor-pointer" @click="openEditModal" />
|
|
||||||
<TrashIcon v-if="can('delete', 'club', 'member')" class="w-5 h-5 cursor-pointer" @click="openDeleteModal" />
|
|
||||||
</div>
|
|
||||||
<div class="p-2">
|
|
||||||
<p>
|
|
||||||
besucht: {{ education.start }} <span v-if="education.end">bis {{ education.end }}</span>
|
|
||||||
</p>
|
|
||||||
<p v-if="education.place">Ort: {{ education.place }}</p>
|
|
||||||
<p v-if="education.note">Notiz: {{ education.note }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { defineAsyncComponent, defineComponent, markRaw, type PropType } from "vue";
|
|
||||||
import { mapState, mapActions } from "pinia";
|
|
||||||
import type { MemberEducationViewModel } from "@/viewmodels/admin/club/member/memberEducation.models";
|
|
||||||
import { PencilIcon, TrashIcon } from "@heroicons/vue/24/outline";
|
|
||||||
import { useModalStore } from "@/stores/modal";
|
|
||||||
import { useAbilityStore } from "@/stores/ability";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
export default defineComponent({
|
|
||||||
props: {
|
|
||||||
education: {
|
|
||||||
type: Object as PropType<MemberEducationViewModel>,
|
|
||||||
default: {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapState(useAbilityStore, ["can"]),
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
...mapActions(useModalStore, ["openModal"]),
|
|
||||||
openEditModal() {
|
|
||||||
this.openModal(
|
|
||||||
markRaw(defineAsyncComponent(() => import("@/components/admin/club/member/MemberEducationEditModal.vue"))),
|
|
||||||
this.education.id
|
|
||||||
);
|
|
||||||
},
|
|
||||||
openDeleteModal() {
|
|
||||||
this.openModal(
|
|
||||||
markRaw(defineAsyncComponent(() => import("@/components/admin/club/member/MemberEducationDeleteModal.vue"))),
|
|
||||||
this.education.id
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
|
@ -1,19 +1,17 @@
|
||||||
<template>
|
<template>
|
||||||
<RouterLink
|
<RouterLink
|
||||||
:to="{ name: 'admin-club-member-overview', params: { memberId: member.id } }"
|
:to="{ name: 'admin-club-member-overview', params: { memberId: member.id } }"
|
||||||
class="flex flex-col h-fit w-full border border-primary rounded-md"
|
class="flex flex-col h-fit w-full border border-primary rounded-md"
|
||||||
>
|
>
|
||||||
<div class="bg-primary p-2 text-white flex flex-row justify-between items-center">
|
<div
|
||||||
|
class="bg-primary p-2 text-white flex flex-row justify-between items-center"
|
||||||
|
>
|
||||||
<p>{{ member.lastname }}, {{ member.firstname }} {{ member.nameaffix ? `- ${member.nameaffix}` : "" }}</p>
|
<p>{{ member.lastname }}, {{ member.firstname }} {{ member.nameaffix ? `- ${member.nameaffix}` : "" }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-2">
|
<div class="p-2">
|
||||||
<p v-if="member.internalId">Interne ID: {{ member.internalId }}</p>
|
<p v-if="member.internalId">Interne ID: {{ member.internalId }}</p>
|
||||||
<p v-if="member.note">Notiz: {{ member.note }}</p>
|
|
||||||
<p>beigetreten: {{ member.firstMembershipEntry?.start }}</p>
|
<p>beigetreten: {{ member.firstMembershipEntry?.start }}</p>
|
||||||
<p v-if="member.lastMembershipEntry?.end">
|
<p v-if="member.lastMembershipEntry?.end">ausgetreten: {{ member.lastMembershipEntry?.end }}, da {{member.lastMembershipEntry?.terminationReason ?? '- kein Grund angegeben'}}</p>
|
||||||
ausgetreten: {{ member.lastMembershipEntry?.end }}, da
|
|
||||||
{{ member.lastMembershipEntry?.terminationReason ?? "- kein Grund angegeben" }}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -63,12 +63,11 @@ export default defineComponent({
|
||||||
};
|
};
|
||||||
this.status = "loading";
|
this.status = "loading";
|
||||||
this.createNewsletter(createNewsletter)
|
this.createNewsletter(createNewsletter)
|
||||||
.then((res) => {
|
.then(() => {
|
||||||
this.status = { status: "success" };
|
this.status = { status: "success" };
|
||||||
this.timeout = setTimeout(() => {
|
this.timeout = setTimeout(() => {
|
||||||
(this.$refs.form as HTMLFormElement).reset();
|
(this.$refs.form as HTMLFormElement).reset();
|
||||||
this.closeModal();
|
this.closeModal();
|
||||||
this.$router.push({ name: "admin-club-newsletter-overview", params: { newsletterId: res.data } });
|
|
||||||
}, 1500);
|
}, 1500);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
|
|
@ -4,15 +4,7 @@
|
||||||
:to="{ name: 'admin-club-newsletter-overview', params: { newsletterId: newsletter.id } }"
|
:to="{ name: 'admin-club-newsletter-overview', params: { newsletterId: newsletter.id } }"
|
||||||
class="bg-primary p-2 text-white flex flex-row justify-between items-center"
|
class="bg-primary p-2 text-white flex flex-row justify-between items-center"
|
||||||
>
|
>
|
||||||
<p>
|
<p>{{ newsletter.title }}</p>
|
||||||
{{ newsletter.title }} ({{
|
|
||||||
new Date(newsletter.createdAt).toLocaleDateString("de-DE", {
|
|
||||||
day: "2-digit",
|
|
||||||
month: "2-digit",
|
|
||||||
year: "numeric",
|
|
||||||
})
|
|
||||||
}})
|
|
||||||
</p>
|
|
||||||
<PaperAirplaneIcon v-if="newsletter.isSent" class="w-5 h-5" />
|
<PaperAirplaneIcon v-if="newsletter.isSent" class="w-5 h-5" />
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
<div class="p-2 max-h-48 overflow-y-auto">
|
<div class="p-2 max-h-48 overflow-y-auto">
|
||||||
|
|
|
@ -66,12 +66,11 @@ export default defineComponent({
|
||||||
};
|
};
|
||||||
this.status = "loading";
|
this.status = "loading";
|
||||||
this.createProtocol(createProtocol)
|
this.createProtocol(createProtocol)
|
||||||
.then((res) => {
|
.then(() => {
|
||||||
this.status = { status: "success" };
|
this.status = { status: "success" };
|
||||||
this.timeout = setTimeout(() => {
|
this.timeout = setTimeout(() => {
|
||||||
(this.$refs.form as HTMLFormElement).reset();
|
(this.$refs.form as HTMLFormElement).reset();
|
||||||
this.closeModal();
|
this.closeModal();
|
||||||
this.$router.push({ name: "admin-club-protocol-overview", params: { protocolId: res.data } });
|
|
||||||
}, 1500);
|
}, 1500);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
|
|
@ -1,81 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="w-full md:max-w-md">
|
|
||||||
<div class="flex flex-col items-center">
|
|
||||||
<p class="text-xl font-medium">Aus-/Fortbildung erstellen</p>
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
<form class="flex flex-col gap-4 py-2" @submit.prevent="triggerCreate">
|
|
||||||
<div>
|
|
||||||
<label for="education">Bezeichnung</label>
|
|
||||||
<input type="text" id="education" required />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label for="description">Beschreibung (optional)</label>
|
|
||||||
<input type="text" id="description" />
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-row gap-2">
|
|
||||||
<button primary type="submit" :disabled="status == 'loading' || status?.status == 'success'">erstellen</button>
|
|
||||||
<Spinner v-if="status == 'loading'" class="my-auto" />
|
|
||||||
<SuccessCheckmark v-else-if="status?.status == 'success'" />
|
|
||||||
<FailureXMark v-else-if="status?.status == 'failed'" />
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<div class="flex flex-row justify-end">
|
|
||||||
<div class="flex flex-row gap-4 py-2">
|
|
||||||
<button primary-outline @click="closeModal" :disabled="status == 'loading' || status?.status == 'success'">
|
|
||||||
abbrechen
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { defineComponent } from "vue";
|
|
||||||
import { mapState, mapActions } from "pinia";
|
|
||||||
import { useModalStore } from "@/stores/modal";
|
|
||||||
import Spinner from "@/components/Spinner.vue";
|
|
||||||
import SuccessCheckmark from "@/components/SuccessCheckmark.vue";
|
|
||||||
import FailureXMark from "@/components/FailureXMark.vue";
|
|
||||||
import { useEducationStore } from "@/stores/admin/configuration/education";
|
|
||||||
import type { CreateEducationViewModel } from "@/viewmodels/admin/configuration/education.models";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
export default defineComponent({
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
status: null as null | "loading" | { status: "success" | "failed"; reason?: string },
|
|
||||||
timeout: undefined as any,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
beforeUnmount() {
|
|
||||||
try {
|
|
||||||
clearTimeout(this.timeout);
|
|
||||||
} catch (error) {}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
...mapActions(useModalStore, ["closeModal"]),
|
|
||||||
...mapActions(useEducationStore, ["createEducation"]),
|
|
||||||
triggerCreate(e: any) {
|
|
||||||
let formData = e.target.elements;
|
|
||||||
let createEducation: CreateEducationViewModel = {
|
|
||||||
education: formData.education.value,
|
|
||||||
description: formData.description.value,
|
|
||||||
};
|
|
||||||
this.status = "loading";
|
|
||||||
this.createEducation(createEducation)
|
|
||||||
.then(() => {
|
|
||||||
this.status = { status: "success" };
|
|
||||||
this.timeout = setTimeout(() => {
|
|
||||||
this.closeModal();
|
|
||||||
}, 1500);
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
this.status = { status: "failed" };
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
|
@ -1,75 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="w-full md:max-w-md">
|
|
||||||
<div class="flex flex-col items-center">
|
|
||||||
<p class="text-xl font-medium">Aus-/Fortbildung {{ education?.education }} löschen?</p>
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
|
|
||||||
<div class="flex flex-row gap-2">
|
|
||||||
<button primary :disabled="status == 'loading' || status?.status == 'success'" @click="triggerDelete">
|
|
||||||
unwiederuflich löschen
|
|
||||||
</button>
|
|
||||||
<Spinner v-if="status == 'loading'" class="my-auto" />
|
|
||||||
<SuccessCheckmark v-else-if="status?.status == 'success'" />
|
|
||||||
<FailureXMark v-else-if="status?.status == 'failed'" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex flex-row justify-end">
|
|
||||||
<div class="flex flex-row gap-4 py-2">
|
|
||||||
<button primary-outline @click="closeModal" :disabled="status == 'loading' || status?.status == 'success'">
|
|
||||||
abbrechen
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { defineComponent } from "vue";
|
|
||||||
import { mapState, mapActions } from "pinia";
|
|
||||||
import { useModalStore } from "@/stores/modal";
|
|
||||||
import Spinner from "@/components/Spinner.vue";
|
|
||||||
import SuccessCheckmark from "@/components/SuccessCheckmark.vue";
|
|
||||||
import FailureXMark from "@/components/FailureXMark.vue";
|
|
||||||
import { useEducationStore } from "@/stores/admin/configuration/education";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
export default defineComponent({
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
status: null as null | "loading" | { status: "success" | "failed"; reason?: string },
|
|
||||||
timeout: undefined as any,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
beforeUnmount() {
|
|
||||||
try {
|
|
||||||
clearTimeout(this.timeout);
|
|
||||||
} catch (error) {}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapState(useModalStore, ["data"]),
|
|
||||||
...mapState(useEducationStore, ["educations"]),
|
|
||||||
education() {
|
|
||||||
return this.educations.find((r) => r.id == this.data);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
...mapActions(useModalStore, ["closeModal"]),
|
|
||||||
...mapActions(useEducationStore, ["deleteEducation"]),
|
|
||||||
triggerDelete() {
|
|
||||||
this.status = "loading";
|
|
||||||
this.deleteEducation(this.data)
|
|
||||||
.then(() => {
|
|
||||||
this.status = { status: "success" };
|
|
||||||
this.timeout = setTimeout(() => {
|
|
||||||
this.closeModal();
|
|
||||||
}, 1500);
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
this.status = { status: "failed" };
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
|
@ -1,55 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="flex flex-col h-fit w-full border border-primary rounded-md">
|
|
||||||
<div class="bg-primary p-2 text-white flex flex-row justify-between items-center">
|
|
||||||
<p>{{ education.education }}</p>
|
|
||||||
<div class="flex flex-row">
|
|
||||||
<RouterLink
|
|
||||||
v-if="can('update', 'configuration', 'education')"
|
|
||||||
:to="{ name: 'admin-configuration-education-edit', params: { id: education.id } }"
|
|
||||||
>
|
|
||||||
<PencilIcon class="w-5 h-5 p-1 box-content cursor-pointer" />
|
|
||||||
</RouterLink>
|
|
||||||
<div v-if="can('delete', 'configuration', 'education')" @click="openDeleteModal">
|
|
||||||
<TrashIcon class="w-5 h-5 p-1 box-content cursor-pointer" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col p-2">
|
|
||||||
<div class="flex flex-row gap-2">
|
|
||||||
<p class="min-w-16">Beschreibung:</p>
|
|
||||||
<p class="grow overflow-hidden">{{ education.description }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { defineComponent, defineAsyncComponent, markRaw, type PropType } from "vue";
|
|
||||||
import { mapState, mapActions } from "pinia";
|
|
||||||
import { PencilIcon, TrashIcon } from "@heroicons/vue/24/outline";
|
|
||||||
import { useAbilityStore } from "@/stores/ability";
|
|
||||||
import { useModalStore } from "@/stores/modal";
|
|
||||||
import type { EducationViewModel } from "@/viewmodels/admin/configuration/education.models";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
export default defineComponent({
|
|
||||||
props: {
|
|
||||||
education: { type: Object as PropType<EducationViewModel>, default: {} },
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapState(useAbilityStore, ["can"]),
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
...mapActions(useModalStore, ["openModal"]),
|
|
||||||
openDeleteModal() {
|
|
||||||
this.openModal(
|
|
||||||
markRaw(
|
|
||||||
defineAsyncComponent(() => import("@/components/admin/configuration/education/DeleteEducationModal.vue"))
|
|
||||||
),
|
|
||||||
this.education.id
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
|
@ -58,7 +58,7 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "app.show_link_to_calendar",
|
key: "app.show_link_to_calendar",
|
||||||
value: formData.show_link_to_calendar.checked,
|
value: formData.show_link_to_calendar.checked || null,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
|
|
|
@ -142,12 +142,6 @@ const router = createRouter({
|
||||||
component: () => import("@/views/admin/club/members/MemberAwards.vue"),
|
component: () => import("@/views/admin/club/members/MemberAwards.vue"),
|
||||||
props: true,
|
props: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: "educations",
|
|
||||||
name: "admin-club-member-educations",
|
|
||||||
component: () => import("@/views/admin/club/members/MemberEducations.vue"),
|
|
||||||
props: true,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "qualifications",
|
path: "qualifications",
|
||||||
name: "admin-club-member-qualifications",
|
name: "admin-club-member-qualifications",
|
||||||
|
@ -367,30 +361,6 @@ const router = createRouter({
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: "education",
|
|
||||||
name: "admin-configuration-education-route",
|
|
||||||
component: () => import("@/views/RouterView.vue"),
|
|
||||||
meta: { type: "read", section: "configuration", module: "education" },
|
|
||||||
beforeEnter: [abilityAndNavUpdate],
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: "",
|
|
||||||
name: "admin-configuration-education",
|
|
||||||
component: () => import("@/views/admin/configuration/education/Education.vue"),
|
|
||||||
meta: { type: "read", section: "configuration", module: "education" },
|
|
||||||
beforeEnter: [abilityAndNavUpdate],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: ":id/edit",
|
|
||||||
name: "admin-configuration-education-edit",
|
|
||||||
component: () => import("@/views/admin/configuration/education/EducationEdit.vue"),
|
|
||||||
meta: { type: "update", section: "configuration", module: "education" },
|
|
||||||
beforeEnter: [abilityAndNavUpdate],
|
|
||||||
props: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "executive-position",
|
path: "executive-position",
|
||||||
name: "admin-configuration-executive_position-route",
|
name: "admin-configuration-executive_position-route",
|
||||||
|
|
|
@ -4,7 +4,6 @@ import { useMemberAwardStore } from "@/stores/admin/club/member/memberAward";
|
||||||
import { useMemberExecutivePositionStore } from "@/stores/admin/club/member/memberExecutivePosition";
|
import { useMemberExecutivePositionStore } from "@/stores/admin/club/member/memberExecutivePosition";
|
||||||
import { useMemberQualificationStore } from "@/stores/admin/club/member/memberQualification";
|
import { useMemberQualificationStore } from "@/stores/admin/club/member/memberQualification";
|
||||||
import { useMembershipStore } from "@/stores/admin/club/member/membership";
|
import { useMembershipStore } from "@/stores/admin/club/member/membership";
|
||||||
import { useMemberEducationStore } from "../stores/admin/club/member/memberEducation";
|
|
||||||
|
|
||||||
export async function setMemberId(to: any, from: any, next: any) {
|
export async function setMemberId(to: any, from: any, next: any) {
|
||||||
const member = useMemberStore();
|
const member = useMemberStore();
|
||||||
|
@ -15,7 +14,6 @@ export async function setMemberId(to: any, from: any, next: any) {
|
||||||
useMemberAwardStore().$reset();
|
useMemberAwardStore().$reset();
|
||||||
useMemberExecutivePositionStore().$reset();
|
useMemberExecutivePositionStore().$reset();
|
||||||
useMemberQualificationStore().$reset();
|
useMemberQualificationStore().$reset();
|
||||||
useMemberEducationStore().$reset();
|
|
||||||
|
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
|
@ -30,7 +28,6 @@ export async function resetMemberStores(to: any, from: any, next: any) {
|
||||||
useMemberAwardStore().$reset();
|
useMemberAwardStore().$reset();
|
||||||
useMemberExecutivePositionStore().$reset();
|
useMemberExecutivePositionStore().$reset();
|
||||||
useMemberQualificationStore().$reset();
|
useMemberQualificationStore().$reset();
|
||||||
useMemberEducationStore().$reset();
|
|
||||||
|
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,7 +106,6 @@ export const useMemberStore = defineStore("member", {
|
||||||
nameaffix: member.nameaffix,
|
nameaffix: member.nameaffix,
|
||||||
birthdate: member.birthdate,
|
birthdate: member.birthdate,
|
||||||
internalId: member.internalId,
|
internalId: member.internalId,
|
||||||
note: member.note,
|
|
||||||
});
|
});
|
||||||
this.fetchMembers();
|
this.fetchMembers();
|
||||||
return result;
|
return result;
|
||||||
|
@ -119,7 +118,6 @@ export const useMemberStore = defineStore("member", {
|
||||||
nameaffix: member.nameaffix,
|
nameaffix: member.nameaffix,
|
||||||
birthdate: member.birthdate,
|
birthdate: member.birthdate,
|
||||||
internalId: member.internalId,
|
internalId: member.internalId,
|
||||||
note: member.note,
|
|
||||||
});
|
});
|
||||||
this.fetchMembers();
|
this.fetchMembers();
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -1,67 +0,0 @@
|
||||||
import { defineStore } from "pinia";
|
|
||||||
import { http } from "@/serverCom";
|
|
||||||
import type { AxiosResponse } from "axios";
|
|
||||||
import { useMemberStore } from "./member";
|
|
||||||
import type {
|
|
||||||
CreateMemberEducationViewModel,
|
|
||||||
MemberEducationViewModel,
|
|
||||||
UpdateMemberEducationViewModel,
|
|
||||||
} from "@/viewmodels/admin/club/member/memberEducation.models";
|
|
||||||
|
|
||||||
export const useMemberEducationStore = defineStore("memberEducation", {
|
|
||||||
state: () => {
|
|
||||||
return {
|
|
||||||
memberEducations: [] as Array<MemberEducationViewModel>,
|
|
||||||
loading: "loading" as "loading" | "fetched" | "failed",
|
|
||||||
};
|
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
fetchMemberEducationsForMember() {
|
|
||||||
const memberId = useMemberStore().activeMember;
|
|
||||||
this.loading = "loading";
|
|
||||||
http
|
|
||||||
.get(`/admin/member/${memberId}/educations`)
|
|
||||||
.then((result) => {
|
|
||||||
this.memberEducations = result.data;
|
|
||||||
this.loading = "fetched";
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
this.loading = "failed";
|
|
||||||
});
|
|
||||||
},
|
|
||||||
fetchMemberEducationById(id: number) {
|
|
||||||
const memberId = useMemberStore().activeMember;
|
|
||||||
return http.get(`/admin/member/${memberId}/education/${id}`);
|
|
||||||
},
|
|
||||||
async createMemberEducation(memberEducation: CreateMemberEducationViewModel): Promise<AxiosResponse<any, any>> {
|
|
||||||
const memberId = useMemberStore().activeMember;
|
|
||||||
const result = await http.post(`/admin/member/${memberId}/education`, {
|
|
||||||
start: memberEducation.start,
|
|
||||||
end: memberEducation.end,
|
|
||||||
place: memberEducation.place,
|
|
||||||
note: memberEducation.note,
|
|
||||||
educationId: memberEducation.educationId,
|
|
||||||
});
|
|
||||||
this.fetchMemberEducationsForMember();
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
async updateMemberEducation(memberEducation: UpdateMemberEducationViewModel): Promise<AxiosResponse<any, any>> {
|
|
||||||
const memberId = useMemberStore().activeMember;
|
|
||||||
const result = await http.patch(`/admin/member/${memberId}/education/${memberEducation.id}`, {
|
|
||||||
start: memberEducation.start,
|
|
||||||
end: memberEducation.end,
|
|
||||||
place: memberEducation.place,
|
|
||||||
note: memberEducation.note,
|
|
||||||
educationId: memberEducation.educationId,
|
|
||||||
});
|
|
||||||
this.fetchMemberEducationsForMember();
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
async deleteMemberEducation(memberEducation: number): Promise<AxiosResponse<any, any>> {
|
|
||||||
const memberId = useMemberStore().activeMember;
|
|
||||||
const result = await http.delete(`/admin/member/${memberId}/education/${memberEducation}`);
|
|
||||||
this.fetchMemberEducationsForMember();
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -7,7 +7,6 @@ import { useMemberStore } from "./member";
|
||||||
import type {
|
import type {
|
||||||
CreateMembershipViewModel,
|
CreateMembershipViewModel,
|
||||||
MembershipStatisticsViewModel,
|
MembershipStatisticsViewModel,
|
||||||
MembershipTotalStatisticsViewModel,
|
|
||||||
MembershipViewModel,
|
MembershipViewModel,
|
||||||
UpdateMembershipViewModel,
|
UpdateMembershipViewModel,
|
||||||
} from "@/viewmodels/admin/club/member/membership.models";
|
} from "@/viewmodels/admin/club/member/membership.models";
|
||||||
|
@ -17,7 +16,6 @@ export const useMembershipStore = defineStore("membership", {
|
||||||
return {
|
return {
|
||||||
memberships: [] as Array<MembershipViewModel>,
|
memberships: [] as Array<MembershipViewModel>,
|
||||||
membershipStatistics: [] as Array<MembershipStatisticsViewModel>,
|
membershipStatistics: [] as Array<MembershipStatisticsViewModel>,
|
||||||
totalMembershipStatistics: undefined as undefined | MembershipTotalStatisticsViewModel,
|
|
||||||
loading: "loading" as "loading" | "fetched" | "failed",
|
loading: "loading" as "loading" | "fetched" | "failed",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -44,15 +42,6 @@ export const useMembershipStore = defineStore("membership", {
|
||||||
})
|
})
|
||||||
.catch((err) => {});
|
.catch((err) => {});
|
||||||
},
|
},
|
||||||
fetchMembershipTotalStatisticsForMember() {
|
|
||||||
const memberId = useMemberStore().activeMember;
|
|
||||||
http
|
|
||||||
.get(`/admin/member/${memberId}/memberships/totalstatistics`)
|
|
||||||
.then((result) => {
|
|
||||||
this.totalMembershipStatistics = result.data;
|
|
||||||
})
|
|
||||||
.catch((err) => {});
|
|
||||||
},
|
|
||||||
fetchMembershipById(id: number) {
|
fetchMembershipById(id: number) {
|
||||||
const memberId = useMemberStore().activeMember;
|
const memberId = useMemberStore().activeMember;
|
||||||
return http.get(`/admin/member/${memberId}/membership/${id}`);
|
return http.get(`/admin/member/${memberId}/membership/${id}`);
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import type { CreateNewsletterViewModel } from "@/viewmodels/admin/club/newsletter/newsletter.models";
|
import type { CreateNewsletterViewModel, SyncNewsletterViewModel } from "@/viewmodels/admin/club/newsletter/newsletter.models";
|
||||||
import { http } from "@/serverCom";
|
import { http } from "@/serverCom";
|
||||||
import type { AxiosResponse } from "axios";
|
import type { AxiosResponse } from "axios";
|
||||||
import type { NewsletterViewModel } from "@/viewmodels/admin/club/newsletter/newsletter.models";
|
import type { NewsletterViewModel } from "@/viewmodels/admin/club/newsletter/newsletter.models";
|
||||||
import cloneDeep from "lodash.clonedeep";
|
import cloneDeep from "lodash.clonedeep";
|
||||||
import isEqual from "lodash.isequal";
|
import isEqual from "lodash.isequal";
|
||||||
|
import difference from "lodash.difference";
|
||||||
|
|
||||||
export const useNewsletterStore = defineStore("newsletter", {
|
export const useNewsletterStore = defineStore("newsletter", {
|
||||||
state: () => {
|
state: () => {
|
||||||
|
@ -71,6 +72,7 @@ export const useNewsletterStore = defineStore("newsletter", {
|
||||||
const result = await http.post(`/admin/newsletter`, {
|
const result = await http.post(`/admin/newsletter`, {
|
||||||
title: newsletter.title,
|
title: newsletter.title,
|
||||||
});
|
});
|
||||||
|
this.fetchNewsletters();
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
async synchronizeActiveNewsletter(): Promise<void> {
|
async synchronizeActiveNewsletter(): Promise<void> {
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import type { CreateProtocolViewModel } from "@/viewmodels/admin/club/protocol/protocol.models";
|
import type { CreateProtocolViewModel, SyncProtocolViewModel } from "@/viewmodels/admin/club/protocol/protocol.models";
|
||||||
import { http } from "@/serverCom";
|
import { http } from "@/serverCom";
|
||||||
import type { AxiosResponse } from "axios";
|
import type { AxiosResponse } from "axios";
|
||||||
import type { ProtocolViewModel } from "@/viewmodels/admin/club/protocol/protocol.models";
|
import type { ProtocolViewModel } from "@/viewmodels/admin/club/protocol/protocol.models";
|
||||||
import cloneDeep from "lodash.clonedeep";
|
import cloneDeep from "lodash.clonedeep";
|
||||||
import isEqual from "lodash.isequal";
|
import isEqual from "lodash.isequal";
|
||||||
|
import difference from "lodash.difference";
|
||||||
|
|
||||||
export const useProtocolStore = defineStore("protocol", {
|
export const useProtocolStore = defineStore("protocol", {
|
||||||
state: () => {
|
state: () => {
|
||||||
|
@ -72,6 +73,7 @@ export const useProtocolStore = defineStore("protocol", {
|
||||||
title: protocol.title,
|
title: protocol.title,
|
||||||
date: protocol.date,
|
date: protocol.date,
|
||||||
});
|
});
|
||||||
|
this.fetchProtocols();
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
async synchronizeActiveProtocol(): Promise<void> {
|
async synchronizeActiveProtocol(): Promise<void> {
|
||||||
|
|
|
@ -71,7 +71,7 @@ export const useProtocolAgendaStore = defineStore("protocolAgenda", {
|
||||||
|
|
||||||
await http
|
await http
|
||||||
.patch(`/admin/protocol/${protocolId}/synchronize/agenda`, {
|
.patch(`/admin/protocol/${protocolId}/synchronize/agenda`, {
|
||||||
agenda: this.agenda,
|
agenda: differenceWith(this.agenda, this.origin, isEqual),
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
this.syncingProtocolAgenda = "synced";
|
this.syncingProtocolAgenda = "synced";
|
||||||
|
|
|
@ -72,7 +72,7 @@ export const useProtocolDecisionStore = defineStore("protocolDecision", {
|
||||||
|
|
||||||
await http
|
await http
|
||||||
.patch(`/admin/protocol/${protocolId}/synchronize/decisions`, {
|
.patch(`/admin/protocol/${protocolId}/synchronize/decisions`, {
|
||||||
decisions: this.decision,
|
decisions: differenceWith(this.decision, this.origin, isEqual),
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
this.syncingProtocolDecision = "synced";
|
this.syncingProtocolDecision = "synced";
|
||||||
|
|
|
@ -75,7 +75,7 @@ export const useProtocolVotingStore = defineStore("protocolVoting", {
|
||||||
|
|
||||||
await http
|
await http
|
||||||
.patch(`/admin/protocol/${protocolId}/synchronize/votings`, {
|
.patch(`/admin/protocol/${protocolId}/synchronize/votings`, {
|
||||||
votings: this.voting,
|
votings: differenceWith(this.voting, this.origin, isEqual),
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
this.syncingProtocolVoting = "synced";
|
this.syncingProtocolVoting = "synced";
|
||||||
|
|
|
@ -1,55 +0,0 @@
|
||||||
import { defineStore } from "pinia";
|
|
||||||
import type {
|
|
||||||
CreateEducationViewModel,
|
|
||||||
UpdateEducationViewModel,
|
|
||||||
EducationViewModel,
|
|
||||||
} from "@/viewmodels/admin/configuration/education.models";
|
|
||||||
import { http } from "@/serverCom";
|
|
||||||
import type { AxiosResponse } from "axios";
|
|
||||||
|
|
||||||
export const useEducationStore = defineStore("education", {
|
|
||||||
state: () => {
|
|
||||||
return {
|
|
||||||
educations: [] as Array<EducationViewModel>,
|
|
||||||
loading: "loading" as "loading" | "fetched" | "failed",
|
|
||||||
};
|
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
fetchEducations() {
|
|
||||||
this.loading = "loading";
|
|
||||||
http
|
|
||||||
.get("/admin/education")
|
|
||||||
.then((result) => {
|
|
||||||
this.educations = result.data;
|
|
||||||
this.loading = "fetched";
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
this.loading = "failed";
|
|
||||||
});
|
|
||||||
},
|
|
||||||
fetchEducationById(id: number): Promise<AxiosResponse<any, any>> {
|
|
||||||
return http.get(`/admin/education/${id}`);
|
|
||||||
},
|
|
||||||
async createEducation(education: CreateEducationViewModel): Promise<AxiosResponse<any, any>> {
|
|
||||||
const result = await http.post(`/admin/education`, {
|
|
||||||
education: education.education,
|
|
||||||
description: education.description,
|
|
||||||
});
|
|
||||||
this.fetchEducations();
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
async updateActiveEducation(education: UpdateEducationViewModel): Promise<AxiosResponse<any, any>> {
|
|
||||||
const result = await http.patch(`/admin/education/${education.id}`, {
|
|
||||||
education: education.education,
|
|
||||||
description: education.description,
|
|
||||||
});
|
|
||||||
this.fetchEducations();
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
async deleteEducation(education: number): Promise<AxiosResponse<any, any>> {
|
|
||||||
const result = await http.delete(`/admin/education/${education}`);
|
|
||||||
this.fetchEducations();
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -112,9 +112,6 @@ export const useNavigationStore = defineStore("navigation", {
|
||||||
...(abilityStore.can("read", "configuration", "qualification")
|
...(abilityStore.can("read", "configuration", "qualification")
|
||||||
? [{ key: "qualification", title: "Qualifikationen" }]
|
? [{ key: "qualification", title: "Qualifikationen" }]
|
||||||
: []),
|
: []),
|
||||||
...(abilityStore.can("read", "configuration", "education")
|
|
||||||
? [{ key: "education", title: "Aus-/Fortbildungen" }]
|
|
||||||
: []),
|
|
||||||
...(abilityStore.can("read", "configuration", "executive_position")
|
...(abilityStore.can("read", "configuration", "executive_position")
|
||||||
? [{ key: "executive_position", title: "Vereinsämter" }]
|
? [{ key: "executive_position", title: "Vereinsämter" }]
|
||||||
: []),
|
: []),
|
||||||
|
|
|
@ -13,7 +13,6 @@ export type PermissionModule =
|
||||||
| "communication_type"
|
| "communication_type"
|
||||||
| "membership_status"
|
| "membership_status"
|
||||||
| "salutation"
|
| "salutation"
|
||||||
| "education"
|
|
||||||
| "calendar_type"
|
| "calendar_type"
|
||||||
| "user"
|
| "user"
|
||||||
| "role"
|
| "role"
|
||||||
|
@ -71,7 +70,6 @@ export const permissionModules: Array<PermissionModule> = [
|
||||||
"communication_type",
|
"communication_type",
|
||||||
"membership_status",
|
"membership_status",
|
||||||
"salutation",
|
"salutation",
|
||||||
"education",
|
|
||||||
"calendar_type",
|
"calendar_type",
|
||||||
"user",
|
"user",
|
||||||
"role",
|
"role",
|
||||||
|
@ -93,7 +91,6 @@ export const sectionsAndModules: SectionsAndModulesObject = {
|
||||||
"communication_type",
|
"communication_type",
|
||||||
"membership_status",
|
"membership_status",
|
||||||
"salutation",
|
"salutation",
|
||||||
"education",
|
|
||||||
"calendar_type",
|
"calendar_type",
|
||||||
"query_store",
|
"query_store",
|
||||||
"template",
|
"template",
|
||||||
|
|
|
@ -15,7 +15,6 @@ export interface MemberViewModel {
|
||||||
sendNewsletter?: CommunicationViewModel;
|
sendNewsletter?: CommunicationViewModel;
|
||||||
smsAlarming?: Array<CommunicationViewModel>;
|
smsAlarming?: Array<CommunicationViewModel>;
|
||||||
preferredCommunication?: Array<CommunicationViewModel>;
|
preferredCommunication?: Array<CommunicationViewModel>;
|
||||||
note?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MemberStatisticsViewModel {
|
export interface MemberStatisticsViewModel {
|
||||||
|
@ -37,7 +36,6 @@ export interface CreateMemberViewModel {
|
||||||
nameaffix: string;
|
nameaffix: string;
|
||||||
birthdate: Date;
|
birthdate: Date;
|
||||||
internalId?: string;
|
internalId?: string;
|
||||||
note?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateMemberViewModel {
|
export interface UpdateMemberViewModel {
|
||||||
|
@ -48,5 +46,4 @@ export interface UpdateMemberViewModel {
|
||||||
nameaffix: string;
|
nameaffix: string;
|
||||||
birthdate: Date;
|
birthdate: Date;
|
||||||
internalId?: string;
|
internalId?: string;
|
||||||
note?: string;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
export interface MemberEducationViewModel {
|
|
||||||
id: number;
|
|
||||||
start: Date;
|
|
||||||
end?: Date;
|
|
||||||
place?: string;
|
|
||||||
note?: string;
|
|
||||||
education: string;
|
|
||||||
educationId: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CreateMemberEducationViewModel {
|
|
||||||
start: Date;
|
|
||||||
end?: Date;
|
|
||||||
place?: string;
|
|
||||||
note?: string;
|
|
||||||
educationId: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UpdateMemberEducationViewModel {
|
|
||||||
id: number;
|
|
||||||
start: Date;
|
|
||||||
end?: Date;
|
|
||||||
place?: string;
|
|
||||||
note?: string;
|
|
||||||
educationId: number;
|
|
||||||
}
|
|
|
@ -21,18 +21,6 @@ export interface MembershipStatisticsViewModel {
|
||||||
memberBirthdate: Date;
|
memberBirthdate: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MembershipTotalStatisticsViewModel {
|
|
||||||
durationInDays: number;
|
|
||||||
durationInYears: number;
|
|
||||||
exactDuration: string;
|
|
||||||
memberId: string;
|
|
||||||
memberSalutation: string;
|
|
||||||
memberFirstname: string;
|
|
||||||
memberLastname: string;
|
|
||||||
memberNameaffix: string;
|
|
||||||
memberBirthdate: Date;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CreateMembershipViewModel {
|
export interface CreateMembershipViewModel {
|
||||||
start: Date;
|
start: Date;
|
||||||
statusId: number;
|
statusId: number;
|
||||||
|
|
|
@ -7,7 +7,6 @@ export interface NewsletterViewModel {
|
||||||
newsletterSignatur: string;
|
newsletterSignatur: string;
|
||||||
isSent: boolean;
|
isSent: boolean;
|
||||||
recipientsByQueryId?: string | null;
|
recipientsByQueryId?: string | null;
|
||||||
createdAt: Date;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateNewsletterViewModel {
|
export interface CreateNewsletterViewModel {
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
export interface EducationViewModel {
|
|
||||||
id: number;
|
|
||||||
education: string;
|
|
||||||
description: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CreateEducationViewModel {
|
|
||||||
education: string;
|
|
||||||
description: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UpdateEducationViewModel {
|
|
||||||
id: number;
|
|
||||||
education: string;
|
|
||||||
description: string | null;
|
|
||||||
}
|
|
|
@ -65,11 +65,6 @@
|
||||||
</div>
|
</div>
|
||||||
<p v-if="loginError" class="text-center">{{ loginError }}</p>
|
<p v-if="loginError" class="text-center">{{ loginError }}</p>
|
||||||
</form>
|
</form>
|
||||||
<div class="flex flex-col gap-2 empty:hidden">
|
|
||||||
<RouterLink v-if="appShow_link_to_calendar" :to="{ name: 'public-calendar' }" button primary-outline>
|
|
||||||
zum Kalender
|
|
||||||
</RouterLink>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<FormBottomBar />
|
<FormBottomBar />
|
||||||
</div>
|
</div>
|
||||||
|
@ -100,7 +95,7 @@ export default defineComponent({
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(useConfigurationStore, ["clubName", "appShow_link_to_calendar"]),
|
...mapState(useConfigurationStore, ["clubName"]),
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.username = localStorage.getItem("username") ?? "";
|
this.username = localStorage.getItem("username") ?? "";
|
||||||
|
|
|
@ -75,10 +75,6 @@
|
||||||
<label for="internalId">Interne ID (optional)</label>
|
<label for="internalId">Interne ID (optional)</label>
|
||||||
<input type="text" id="internalId" v-model="member.internalId" />
|
<input type="text" id="internalId" v-model="member.internalId" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<label for="note">Notiz (optional)</label>
|
|
||||||
<textarea type="text" id="note" v-model="member.note" />
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-row justify-end gap-2">
|
<div class="flex flex-row justify-end gap-2">
|
||||||
<button primary-outline type="reset" class="w-fit!" :disabled="canSaveOrReset" @click="resetForm">
|
<button primary-outline type="reset" class="w-fit!" :disabled="canSaveOrReset" @click="resetForm">
|
||||||
verwerfen
|
verwerfen
|
||||||
|
@ -97,6 +93,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent } from "vue";
|
||||||
import { mapActions, mapState } from "pinia";
|
import { mapActions, mapState } from "pinia";
|
||||||
|
import MainTemplate from "@/templates/Main.vue";
|
||||||
import { useMemberStore } from "@/stores/admin/club/member/member";
|
import { useMemberStore } from "@/stores/admin/club/member/member";
|
||||||
import type { MemberViewModel, UpdateMemberViewModel } from "@/viewmodels/admin/club/member/member.models";
|
import type { MemberViewModel, UpdateMemberViewModel } from "@/viewmodels/admin/club/member/member.models";
|
||||||
import Spinner from "@/components/Spinner.vue";
|
import Spinner from "@/components/Spinner.vue";
|
||||||
|
@ -166,7 +163,6 @@ export default defineComponent({
|
||||||
nameaffix: formData.nameaffix.value,
|
nameaffix: formData.nameaffix.value,
|
||||||
birthdate: formData.birthdate.value,
|
birthdate: formData.birthdate.value,
|
||||||
internalId: formData.internalId.value,
|
internalId: formData.internalId.value,
|
||||||
note: formData.note.value,
|
|
||||||
};
|
};
|
||||||
this.status = "loading";
|
this.status = "loading";
|
||||||
this.updateActiveMember(updateMember)
|
this.updateActiveMember(updateMember)
|
||||||
|
|
|
@ -1,51 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="flex flex-col gap-2 h-full w-full overflow-y-auto">
|
|
||||||
<div v-if="memberEducations != null" class="flex flex-col gap-2 w-full">
|
|
||||||
<MemberEducationListItem v-for="education in memberEducations" :key="education.id" :education="education" />
|
|
||||||
</div>
|
|
||||||
<Spinner v-if="loading == 'loading'" class="mx-auto" />
|
|
||||||
<p v-else-if="loading == 'failed'" @click="fetchItem" class="cursor-pointer">↺ laden fehlgeschlagen</p>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-row gap-4">
|
|
||||||
<button v-if="can('create', 'club', 'member')" primary class="w-fit!" @click="openCreateModal">
|
|
||||||
Aus-/Fortbildung hinzufügen
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { defineAsyncComponent, defineComponent, markRaw } from "vue";
|
|
||||||
import { mapActions, mapState } from "pinia";
|
|
||||||
import Spinner from "@/components/Spinner.vue";
|
|
||||||
import { useMemberEducationStore } from "@/stores/admin/club/member/memberEducation";
|
|
||||||
import MemberEducationListItem from "@/components/admin/club/member/MemberEducationListItem.vue";
|
|
||||||
import { useModalStore } from "@/stores/modal";
|
|
||||||
import { useAbilityStore } from "@/stores/ability";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
export default defineComponent({
|
|
||||||
props: {
|
|
||||||
memberId: String,
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapState(useMemberEducationStore, ["memberEducations", "loading"]),
|
|
||||||
...mapState(useAbilityStore, ["can"]),
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.fetchItem();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
...mapActions(useMemberEducationStore, ["fetchMemberEducationsForMember"]),
|
|
||||||
...mapActions(useModalStore, ["openModal"]),
|
|
||||||
fetchItem() {
|
|
||||||
this.fetchMemberEducationsForMember();
|
|
||||||
},
|
|
||||||
openCreateModal() {
|
|
||||||
this.openModal(
|
|
||||||
markRaw(defineAsyncComponent(() => import("@/components/admin/club/member/MemberEducationCreateModal.vue")))
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
|
@ -25,19 +25,9 @@
|
||||||
<label for="birthdate">Geburtsdatum</label>
|
<label for="birthdate">Geburtsdatum</label>
|
||||||
<input type="date" id="birthdate" :value="activeMemberObj.birthdate" readonly />
|
<input type="date" id="birthdate" :value="activeMemberObj.birthdate" readonly />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div v-if="membershipStatistics.length != 0">
|
||||||
<label for="note">Notiz</label>
|
|
||||||
<textarea type="text" id="note" v-model="activeMemberObj.note" readonly />
|
|
||||||
</div>
|
|
||||||
<div v-if="membershipStatistics.length != 0 || totalMembershipStatistics != undefined">
|
|
||||||
<p>Statistiken zur Mitgliedschaft</p>
|
<p>Statistiken zur Mitgliedschaft</p>
|
||||||
<div class="flex flex-col h-fit w-full rounded-md overflow-hidden divide-y divide-white">
|
<div class="flex flex-col h-fit w-full border border-primary rounded-md">
|
||||||
<div class="bg-primary p-2 text-white flex flex-row justify-between items-center">
|
|
||||||
<p>
|
|
||||||
gesamt {{ totalMembershipStatistics?.durationInDays }} Tage
|
|
||||||
<span class="whitespace-nowrap"> ~> {{ totalMembershipStatistics?.exactDuration }}</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
v-for="stat in membershipStatistics"
|
v-for="stat in membershipStatistics"
|
||||||
class="bg-primary p-2 text-white flex flex-row justify-between items-center"
|
class="bg-primary p-2 text-white flex flex-row justify-between items-center"
|
||||||
|
@ -159,20 +149,16 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(useMemberStore, ["activeMemberObj", "activeMemberStatistics", "loadingActive"]),
|
...mapState(useMemberStore, ["activeMemberObj", "activeMemberStatistics", "loadingActive"]),
|
||||||
...mapState(useMembershipStore, ["membershipStatistics", "totalMembershipStatistics"]),
|
...mapState(useMembershipStore, ["membershipStatistics"]),
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.fetchMemberByActiveId();
|
this.fetchMemberByActiveId();
|
||||||
this.fetchMemberStatisticsByActiveId();
|
this.fetchMemberStatisticsByActiveId();
|
||||||
this.fetchMembershipStatisticsForMember();
|
this.fetchMembershipStatisticsForMember();
|
||||||
this.fetchMembershipTotalStatisticsForMember();
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(useMemberStore, ["fetchMemberByActiveId", "fetchMemberStatisticsByActiveId"]),
|
...mapActions(useMemberStore, ["fetchMemberByActiveId", "fetchMemberStatisticsByActiveId"]),
|
||||||
...mapActions(useMembershipStore, [
|
...mapActions(useMembershipStore, ["fetchMembershipStatisticsForMember"]),
|
||||||
"fetchMembershipStatisticsForMember",
|
|
||||||
"fetchMembershipTotalStatisticsForMember",
|
|
||||||
]),
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
<template #diffMain>
|
<template #diffMain>
|
||||||
<div class="flex flex-col gap-2 grow px-7 overflow-hidden">
|
<div class="flex flex-col gap-2 grow px-7 overflow-hidden">
|
||||||
<div class="flex flex-col grow gap-2 overflow-hidden">
|
<div class="flex flex-col grow gap-2 overflow-hidden">
|
||||||
<div class="w-full flex flex-row max-lg:flex-wrap justify-center items-stretch">
|
<div class="w-full flex flex-row max-lg:flex-wrap justify-center">
|
||||||
<RouterLink
|
<RouterLink
|
||||||
v-for="tab in tabs"
|
v-for="tab in tabs"
|
||||||
:key="tab.route"
|
:key="tab.route"
|
||||||
|
@ -31,7 +31,7 @@
|
||||||
>
|
>
|
||||||
<p
|
<p
|
||||||
:class="[
|
:class="[
|
||||||
'flex w-full h-full items-center justify-center rounded-lg py-2.5 text-sm text-center font-medium leading-5 focus:ring-0 outline-hidden',
|
'w-full rounded-lg py-2.5 text-sm text-center font-medium leading-5 focus:ring-0 outline-hidden',
|
||||||
isActive ? 'bg-red-200 shadow-sm border-b-2 border-primary rounded-b-none' : ' hover:bg-red-200',
|
isActive ? 'bg-red-200 shadow-sm border-b-2 border-primary rounded-b-none' : ' hover:bg-red-200',
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
|
@ -68,9 +68,8 @@ export default defineComponent({
|
||||||
{ route: "admin-club-member-overview", title: "Übersicht" },
|
{ route: "admin-club-member-overview", title: "Übersicht" },
|
||||||
{ route: "admin-club-member-membership", title: "Mitgliedschaft" },
|
{ route: "admin-club-member-membership", title: "Mitgliedschaft" },
|
||||||
{ route: "admin-club-member-communication", title: "Kommunikation" },
|
{ route: "admin-club-member-communication", title: "Kommunikation" },
|
||||||
{ route: "admin-club-member-awards", title: "Auszeichnungen / Ehrungen" },
|
{ route: "admin-club-member-awards", title: "Auszeichnungen" },
|
||||||
{ route: "admin-club-member-educations", title: "Aus- / Fortbildungen" },
|
{ route: "admin-club-member-qualifications", title: "Qualifikationen" },
|
||||||
{ route: "admin-club-member-qualifications", title: "Qualifikationen / Funktionen" },
|
|
||||||
{ route: "admin-club-member-positions", title: "Vereinsämter" },
|
{ route: "admin-club-member-positions", title: "Vereinsämter" },
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
|
@ -37,15 +37,11 @@
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
</p>
|
</p>
|
||||||
<DoubleConfirmClick
|
<TrashIcon
|
||||||
v-if="can('create', 'club', 'newsletter')"
|
v-if="can('create', 'club', 'newsletter')"
|
||||||
light
|
class="w-5 h-5 p-1 box-content cursor-pointer text-white"
|
||||||
v-slot="{ isSensitive }"
|
@click.prevent="removeSelected(item.calendarId)"
|
||||||
@click:submit="removeSelected(item.calendarId)"
|
/>
|
||||||
>
|
|
||||||
<TrashIcon v-if="!isSensitive" class="h-5 w-5" />
|
|
||||||
<TrashIconSolid v-else class="h-5 w-5" />
|
|
||||||
</DoubleConfirmClick>
|
|
||||||
</summary>
|
</summary>
|
||||||
<div class="flex flex-col gap-2 px-1">
|
<div class="flex flex-col gap-2 px-1">
|
||||||
<input
|
<input
|
||||||
|
@ -115,10 +111,8 @@ import { useAbilityStore } from "@/stores/ability";
|
||||||
import { useCalendarStore } from "@/stores/admin/club/calendar";
|
import { useCalendarStore } from "@/stores/admin/club/calendar";
|
||||||
import type { CalendarViewModel } from "@/viewmodels/admin/club/calendar.models";
|
import type { CalendarViewModel } from "@/viewmodels/admin/club/calendar.models";
|
||||||
import { TrashIcon } from "@heroicons/vue/24/outline";
|
import { TrashIcon } from "@heroicons/vue/24/outline";
|
||||||
import { TrashIcon as TrashIconSolid } from "@heroicons/vue/24/solid";
|
|
||||||
import cloneDeep from "lodash.clonedeep";
|
import cloneDeep from "lodash.clonedeep";
|
||||||
import type { NewsletterDatesViewModel } from "@/viewmodels/admin/club/newsletter/newsletterDates.models";
|
import type { NewsletterDatesViewModel } from "@/viewmodels/admin/club/newsletter/newsletterDates.models";
|
||||||
import DoubleConfirmClick from "@/components/DoubleConfirmClick.vue";
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
|
|
@ -42,15 +42,11 @@
|
||||||
<p>Newsletter senden an Typ: {{ member.sendNewsletter?.type.type ?? "---" }}</p>
|
<p>Newsletter senden an Typ: {{ member.sendNewsletter?.type.type ?? "---" }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DoubleConfirmClick
|
<TrashIcon
|
||||||
v-if="can('create', 'club', 'newsletter') && showMemberSelect"
|
v-if="can('create', 'club', 'newsletter') && showMemberSelect"
|
||||||
light
|
class="w-5 h-5 p-1 box-content cursor-pointer"
|
||||||
v-slot="{ isSensitive }"
|
@click="removeSelected(member.id)"
|
||||||
@click:submit="removeSelected(member.id)"
|
/>
|
||||||
>
|
|
||||||
<TrashIcon v-if="!isSensitive" class="h-5 w-5" />
|
|
||||||
<TrashIconSolid v-else class="h-5 w-5" />
|
|
||||||
</DoubleConfirmClick>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -65,8 +61,17 @@
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent } from "vue";
|
||||||
import { mapActions, mapState, mapWritableState } from "pinia";
|
import { mapActions, mapState, mapWritableState } from "pinia";
|
||||||
import Spinner from "@/components/Spinner.vue";
|
import Spinner from "@/components/Spinner.vue";
|
||||||
|
import {
|
||||||
|
Combobox,
|
||||||
|
ComboboxLabel,
|
||||||
|
ComboboxInput,
|
||||||
|
ComboboxButton,
|
||||||
|
ComboboxOptions,
|
||||||
|
ComboboxOption,
|
||||||
|
TransitionRoot,
|
||||||
|
} from "@headlessui/vue";
|
||||||
|
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/vue/20/solid";
|
||||||
import { ArchiveBoxIcon, ExclamationTriangleIcon, TrashIcon, UserPlusIcon } from "@heroicons/vue/24/outline";
|
import { ArchiveBoxIcon, ExclamationTriangleIcon, TrashIcon, UserPlusIcon } from "@heroicons/vue/24/outline";
|
||||||
import { TrashIcon as TrashIconSolid } from "@heroicons/vue/24/solid";
|
|
||||||
import { useMemberStore } from "@/stores/admin/club/member/member";
|
import { useMemberStore } from "@/stores/admin/club/member/member";
|
||||||
import type { MemberViewModel } from "@/viewmodels/admin/club/member/member.models";
|
import type { MemberViewModel } from "@/viewmodels/admin/club/member/member.models";
|
||||||
import { useNewsletterStore } from "@/stores/admin/club/newsletter/newsletter";
|
import { useNewsletterStore } from "@/stores/admin/club/newsletter/newsletter";
|
||||||
|
@ -74,9 +79,9 @@ import { useNewsletterRecipientsStore } from "@/stores/admin/club/newsletter/new
|
||||||
import { useAbilityStore } from "@/stores/ability";
|
import { useAbilityStore } from "@/stores/ability";
|
||||||
import { useQueryStoreStore } from "@/stores/admin/configuration/queryStore";
|
import { useQueryStoreStore } from "@/stores/admin/configuration/queryStore";
|
||||||
import { useQueryBuilderStore } from "@/stores/admin/club/queryBuilder";
|
import { useQueryBuilderStore } from "@/stores/admin/club/queryBuilder";
|
||||||
|
import cloneDeep from "lodash.clonedeep";
|
||||||
import MemberSearchSelect from "@/components/admin/MemberSearchSelect.vue";
|
import MemberSearchSelect from "@/components/admin/MemberSearchSelect.vue";
|
||||||
import type { FieldType } from "@/types/dynamicQueries";
|
import type { FieldType } from "@/types/dynamicQueries";
|
||||||
import DoubleConfirmClick from "@/components/DoubleConfirmClick.vue";
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
|
|
@ -31,16 +31,6 @@
|
||||||
:disabled="!can('create', 'club', 'protocol')"
|
:disabled="!can('create', 'club', 'protocol')"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DoubleConfirmClick
|
|
||||||
v-if="can('create', 'club', 'protocol')"
|
|
||||||
light
|
|
||||||
v-slot="{ isSensitive }"
|
|
||||||
@click:submit="removeFromArray(item.id)"
|
|
||||||
>
|
|
||||||
<TrashIcon v-if="!isSensitive" class="h-5 w-5" />
|
|
||||||
<TrashIconSolid v-else class="h-5 w-5" />
|
|
||||||
</DoubleConfirmClick>
|
|
||||||
|
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<ChevronUpIcon
|
<ChevronUpIcon
|
||||||
v-if="index != 0"
|
v-if="index != 0"
|
||||||
|
@ -83,9 +73,7 @@ import "@vueup/vue-quill/dist/vue-quill.snow.css";
|
||||||
import { toolbarOptions } from "@/helpers/quillConfig";
|
import { toolbarOptions } from "@/helpers/quillConfig";
|
||||||
import { useProtocolAgendaStore } from "@/stores/admin/club/protocol/protocolAgenda";
|
import { useProtocolAgendaStore } from "@/stores/admin/club/protocol/protocolAgenda";
|
||||||
import { useAbilityStore } from "@/stores/ability";
|
import { useAbilityStore } from "@/stores/ability";
|
||||||
import { ChevronDownIcon, ChevronUpIcon, TrashIcon } from "@heroicons/vue/24/outline";
|
import { ChevronDownIcon, ChevronUpIcon } from "@heroicons/vue/24/outline";
|
||||||
import { TrashIcon as TrashIconSolid } from "@heroicons/vue/24/solid";
|
|
||||||
import DoubleConfirmClick from "@/components/DoubleConfirmClick.vue";
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
@ -121,9 +109,6 @@ export default defineComponent({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
removeFromArray(thisId: number) {
|
|
||||||
this.agenda = this.agenda.filter((item) => item.id !== thisId);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -31,16 +31,6 @@
|
||||||
:disabled="!can('create', 'club', 'protocol')"
|
:disabled="!can('create', 'club', 'protocol')"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DoubleConfirmClick
|
|
||||||
v-if="can('create', 'club', 'protocol')"
|
|
||||||
light
|
|
||||||
v-slot="{ isSensitive }"
|
|
||||||
@click:submit="removeFromArray(item.id)"
|
|
||||||
>
|
|
||||||
<TrashIcon v-if="!isSensitive" class="h-5 w-5" />
|
|
||||||
<TrashIconSolid v-else class="h-5 w-5" />
|
|
||||||
</DoubleConfirmClick>
|
|
||||||
|
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<ChevronUpIcon
|
<ChevronUpIcon
|
||||||
v-if="index != 0"
|
v-if="index != 0"
|
||||||
|
@ -83,9 +73,7 @@ import "@vueup/vue-quill/dist/vue-quill.snow.css";
|
||||||
import { toolbarOptions } from "@/helpers/quillConfig";
|
import { toolbarOptions } from "@/helpers/quillConfig";
|
||||||
import { useProtocolDecisionStore } from "@/stores/admin/club/protocol/protocolDecision";
|
import { useProtocolDecisionStore } from "@/stores/admin/club/protocol/protocolDecision";
|
||||||
import { useAbilityStore } from "@/stores/ability";
|
import { useAbilityStore } from "@/stores/ability";
|
||||||
import { ChevronDownIcon, ChevronUpIcon, TrashIcon } from "@heroicons/vue/24/outline";
|
import { ChevronDownIcon, ChevronUpIcon } from "@heroicons/vue/24/outline";
|
||||||
import { TrashIcon as TrashIconSolid } from "@heroicons/vue/24/solid";
|
|
||||||
import DoubleConfirmClick from "@/components/DoubleConfirmClick.vue";
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
@ -121,9 +109,6 @@ export default defineComponent({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
removeFromArray(thisId: number) {
|
|
||||||
this.decision = this.decision.filter((item) => item.id !== thisId);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -42,15 +42,11 @@
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<DoubleConfirmClick
|
<TrashIcon
|
||||||
v-if="can('create', 'club', 'protocol')"
|
v-if="can('create', 'club', 'protocol')"
|
||||||
light
|
class="w-5 h-5 p-1 box-content cursor-pointer"
|
||||||
v-slot="{ isSensitive }"
|
@click="removeSelected(member.memberId)"
|
||||||
@click:submit="removeSelected(member.memberId)"
|
/>
|
||||||
>
|
|
||||||
<TrashIcon v-if="!isSensitive" class="h-5 w-5" />
|
|
||||||
<TrashIconSolid v-else class="h-5 w-5" />
|
|
||||||
</DoubleConfirmClick>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -61,12 +57,10 @@ import { defineComponent } from "vue";
|
||||||
import { mapActions, mapState, mapWritableState } from "pinia";
|
import { mapActions, mapState, mapWritableState } from "pinia";
|
||||||
import Spinner from "@/components/Spinner.vue";
|
import Spinner from "@/components/Spinner.vue";
|
||||||
import { TrashIcon } from "@heroicons/vue/24/outline";
|
import { TrashIcon } from "@heroicons/vue/24/outline";
|
||||||
import { TrashIcon as TrashIconSolid } from "@heroicons/vue/24/solid";
|
|
||||||
import { useProtocolPresenceStore } from "@/stores/admin/club/protocol/protocolPresence";
|
import { useProtocolPresenceStore } from "@/stores/admin/club/protocol/protocolPresence";
|
||||||
import { useAbilityStore } from "@/stores/ability";
|
import { useAbilityStore } from "@/stores/ability";
|
||||||
import MemberSearchSelect from "@/components/admin/MemberSearchSelect.vue";
|
import MemberSearchSelect from "@/components/admin/MemberSearchSelect.vue";
|
||||||
import type { MemberViewModel } from "@/viewmodels/admin/club/member/member.models";
|
import type { MemberViewModel } from "@/viewmodels/admin/club/member/member.models";
|
||||||
import DoubleConfirmClick from "@/components/DoubleConfirmClick.vue";
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
|
|
@ -31,16 +31,6 @@
|
||||||
:disabled="!can('create', 'club', 'protocol')"
|
:disabled="!can('create', 'club', 'protocol')"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DoubleConfirmClick
|
|
||||||
v-if="can('create', 'club', 'protocol')"
|
|
||||||
light
|
|
||||||
v-slot="{ isSensitive }"
|
|
||||||
@click:submit="removeFromArray(item.id)"
|
|
||||||
>
|
|
||||||
<TrashIcon v-if="!isSensitive" class="h-5 w-5" />
|
|
||||||
<TrashIconSolid v-else class="h-5 w-5" />
|
|
||||||
</DoubleConfirmClick>
|
|
||||||
|
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<ChevronUpIcon
|
<ChevronUpIcon
|
||||||
v-if="index != 0"
|
v-if="index != 0"
|
||||||
|
@ -93,16 +83,14 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent } from "vue";
|
||||||
import { mapActions, mapState, mapWritableState } from "pinia";
|
import { mapActions, mapState } from "pinia";
|
||||||
import Spinner from "@/components/Spinner.vue";
|
import Spinner from "@/components/Spinner.vue";
|
||||||
import { QuillEditor } from "@vueup/vue-quill";
|
import { QuillEditor } from "@vueup/vue-quill";
|
||||||
import "@vueup/vue-quill/dist/vue-quill.snow.css";
|
import "@vueup/vue-quill/dist/vue-quill.snow.css";
|
||||||
import { toolbarOptions } from "@/helpers/quillConfig";
|
import { toolbarOptions } from "@/helpers/quillConfig";
|
||||||
import { useProtocolVotingStore } from "@/stores/admin/club/protocol/protocolVoting";
|
import { useProtocolVotingStore } from "@/stores/admin/club/protocol/protocolVoting";
|
||||||
import { useAbilityStore } from "@/stores/ability";
|
import { useAbilityStore } from "@/stores/ability";
|
||||||
import { ChevronDownIcon, ChevronUpIcon, TrashIcon } from "@heroicons/vue/24/outline";
|
import { ChevronDownIcon, ChevronUpIcon } from "@heroicons/vue/24/outline";
|
||||||
import { TrashIcon as TrashIconSolid } from "@heroicons/vue/24/solid";
|
|
||||||
import DoubleConfirmClick from "@/components/DoubleConfirmClick.vue";
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
@ -111,7 +99,7 @@ export default defineComponent({
|
||||||
protocolId: String,
|
protocolId: String,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapWritableState(useProtocolVotingStore, ["voting", "loading"]),
|
...mapState(useProtocolVotingStore, ["voting", "loading"]),
|
||||||
...mapState(useAbilityStore, ["can"]),
|
...mapState(useAbilityStore, ["can"]),
|
||||||
sortedVoting() {
|
sortedVoting() {
|
||||||
return this.voting.slice().sort((a, b) => a.sort - b.sort);
|
return this.voting.slice().sort((a, b) => a.sort - b.sort);
|
||||||
|
@ -138,9 +126,6 @@ export default defineComponent({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
removeFromArray(thisId: number) {
|
|
||||||
this.voting = this.voting.filter((item) => item.id !== thisId);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,49 +0,0 @@
|
||||||
<template>
|
|
||||||
<MainTemplate title="Aus-/Fortbildungen">
|
|
||||||
<template #diffMain>
|
|
||||||
<div class="flex flex-col gap-4 h-full pl-7">
|
|
||||||
<div class="flex flex-col gap-2 grow overflow-y-scroll pr-7">
|
|
||||||
<EducationListItem v-for="education in educations" :key="education.id" :education="education" />
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-row gap-4">
|
|
||||||
<button v-if="can('create', 'configuration', 'education')" primary class="w-fit!" @click="openCreateModal">
|
|
||||||
Aus-/Fortbildung erstellen
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</MainTemplate>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { defineComponent, defineAsyncComponent, markRaw } from "vue";
|
|
||||||
import { mapState, mapActions } from "pinia";
|
|
||||||
import MainTemplate from "@/templates/Main.vue";
|
|
||||||
import { useEducationStore } from "@/stores/admin/configuration/education";
|
|
||||||
import EducationListItem from "@/components/admin/configuration/education/EducationListItem.vue";
|
|
||||||
import { useModalStore } from "@/stores/modal";
|
|
||||||
import { useAbilityStore } from "@/stores/ability";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
export default defineComponent({
|
|
||||||
computed: {
|
|
||||||
...mapState(useEducationStore, ["educations"]),
|
|
||||||
...mapState(useAbilityStore, ["can"]),
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.fetchEducations();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
...mapActions(useEducationStore, ["fetchEducations"]),
|
|
||||||
...mapActions(useModalStore, ["openModal"]),
|
|
||||||
openCreateModal() {
|
|
||||||
this.openModal(
|
|
||||||
markRaw(
|
|
||||||
defineAsyncComponent(() => import("@/components/admin/configuration/education/CreateEducationModal.vue"))
|
|
||||||
)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
|
@ -1,120 +0,0 @@
|
||||||
<template>
|
|
||||||
<MainTemplate :title="`Aus-/Fortbildung ${origin?.education} - Daten bearbeiten`">
|
|
||||||
<template #headerInsert>
|
|
||||||
<RouterLink to="../" class="text-primary">zurück zur Liste (abbrechen)</RouterLink>
|
|
||||||
</template>
|
|
||||||
<template #main>
|
|
||||||
<Spinner v-if="loading == 'loading'" class="mx-auto" />
|
|
||||||
<p v-else-if="loading == 'failed'">laden fehlgeschlagen</p>
|
|
||||||
<form
|
|
||||||
v-else-if="education != null"
|
|
||||||
class="flex flex-col gap-4 py-2 w-full max-w-xl mx-auto"
|
|
||||||
@submit.prevent="triggerUpdate"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<label for="education">Bezeichnung</label>
|
|
||||||
<input type="text" id="education" required v-model="education.education" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label for="description">Beschreibung (optional)</label>
|
|
||||||
<input type="text" id="description" v-model="education.description" />
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-row justify-end gap-2">
|
|
||||||
<button primary-outline type="reset" class="w-fit!" :disabled="canSaveOrReset" @click="resetForm">
|
|
||||||
verwerfen
|
|
||||||
</button>
|
|
||||||
<button primary type="submit" class="w-fit!" :disabled="status == 'loading' || canSaveOrReset">
|
|
||||||
speichern
|
|
||||||
</button>
|
|
||||||
<Spinner v-if="status == 'loading'" class="my-auto" />
|
|
||||||
<SuccessCheckmark v-else-if="status?.status == 'success'" />
|
|
||||||
<FailureXMark v-else-if="status?.status == 'failed'" />
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</template>
|
|
||||||
</MainTemplate>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { defineComponent } from "vue";
|
|
||||||
import { mapState, mapActions } from "pinia";
|
|
||||||
import MainTemplate from "@/templates/Main.vue";
|
|
||||||
import { useEducationStore } from "@/stores/admin/configuration/education";
|
|
||||||
import Spinner from "@/components/Spinner.vue";
|
|
||||||
import SuccessCheckmark from "@/components/SuccessCheckmark.vue";
|
|
||||||
import FailureXMark from "@/components/FailureXMark.vue";
|
|
||||||
import { RouterLink } from "vue-router";
|
|
||||||
import type { UpdateEducationViewModel, EducationViewModel } from "@/viewmodels/admin/configuration/education.models";
|
|
||||||
import cloneDeep from "lodash.clonedeep";
|
|
||||||
import isEqual from "lodash.isequal";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
export default defineComponent({
|
|
||||||
props: {
|
|
||||||
id: String,
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
loading: "loading" as "loading" | "fetched" | "failed",
|
|
||||||
status: null as null | "loading" | { status: "success" | "failed"; reason?: string },
|
|
||||||
origin: null as null | EducationViewModel,
|
|
||||||
education: null as null | EducationViewModel,
|
|
||||||
timeout: null as any,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
canSaveOrReset(): boolean {
|
|
||||||
return isEqual(this.origin, this.education);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.fetchItem();
|
|
||||||
},
|
|
||||||
beforeUnmount() {
|
|
||||||
try {
|
|
||||||
clearTimeout(this.timeout);
|
|
||||||
} catch (error) {}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
...mapActions(useEducationStore, ["fetchEducationById", "updateActiveEducation"]),
|
|
||||||
resetForm() {
|
|
||||||
this.education = cloneDeep(this.origin);
|
|
||||||
},
|
|
||||||
fetchItem() {
|
|
||||||
this.fetchEducationById(parseInt(this.id ?? ""))
|
|
||||||
.then((result) => {
|
|
||||||
this.education = result.data;
|
|
||||||
this.origin = cloneDeep(result.data);
|
|
||||||
this.loading = "fetched";
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
this.loading = "failed";
|
|
||||||
});
|
|
||||||
},
|
|
||||||
triggerUpdate(e: any) {
|
|
||||||
if (this.education == null) return;
|
|
||||||
let formData = e.target.elements;
|
|
||||||
let updateEducation: UpdateEducationViewModel = {
|
|
||||||
id: this.education.id,
|
|
||||||
education: formData.education.value,
|
|
||||||
description: formData.description.value,
|
|
||||||
};
|
|
||||||
this.status = "loading";
|
|
||||||
this.updateActiveEducation(updateEducation)
|
|
||||||
.then(() => {
|
|
||||||
this.fetchItem();
|
|
||||||
this.status = { status: "success" };
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
this.status = { status: "failed" };
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
this.timeout = setTimeout(() => {
|
|
||||||
this.status = null;
|
|
||||||
}, 2000);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
|
@ -4,12 +4,7 @@
|
||||||
<div class="h-full flex flex-col px-7 overflow-hidden">
|
<div class="h-full flex flex-col px-7 overflow-hidden">
|
||||||
<div class="h-1/2 flex flex-col gap-2 p-2 border border-gray-300 rounded-t-md">
|
<div class="h-1/2 flex flex-col gap-2 p-2 border border-gray-300 rounded-t-md">
|
||||||
<div class="flex flex-row justify-between border-b-2 border-gray-300">
|
<div class="flex flex-row justify-between border-b-2 border-gray-300">
|
||||||
<h1 class="text-xl font-semibold">
|
<h1 class="text-xl font-semibold">Client</h1>
|
||||||
Client
|
|
||||||
<small v-if="newerClientVersions.length != 0">
|
|
||||||
({{ newerClientVersions.length }} neue Version{{ newerClientVersions.length != 1 ? "en" : "" }})
|
|
||||||
</small>
|
|
||||||
</h1>
|
|
||||||
<p>
|
<p>
|
||||||
V{{ clientVersion }} ({{
|
V{{ clientVersion }} ({{
|
||||||
new Date(clientVersionRelease).toLocaleDateString("de", {
|
new Date(clientVersionRelease).toLocaleDateString("de", {
|
||||||
|
@ -43,12 +38,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="h-1/2 flex flex-col gap-2 p-2 border border-gray-300 rounded-b-md">
|
<div class="h-1/2 flex flex-col gap-2 p-2 border border-gray-300 rounded-b-md">
|
||||||
<div class="flex flex-row justify-between border-b-2 border-gray-300">
|
<div class="flex flex-row justify-between border-b-2 border-gray-300">
|
||||||
<h1 class="text-xl font-semibold">
|
<h1 class="text-xl font-semibold">Server</h1>
|
||||||
Server
|
|
||||||
<small v-if="newerServerVersions.length != 0">
|
|
||||||
({{ newerServerVersions.length }} neue Version{{ newerServerVersions.length != 1 ? "en" : "" }})
|
|
||||||
</small>
|
|
||||||
</h1>
|
|
||||||
<p>
|
<p>
|
||||||
V{{ serverVersion }} ({{
|
V{{ serverVersion }} ({{
|
||||||
new Date(serverVersionRelease).toLocaleDateString("de", {
|
new Date(serverVersionRelease).toLocaleDateString("de", {
|
||||||
|
|
Loading…
Add table
Reference in a new issue