This commit is contained in:
Julian Krauser 2024-11-24 15:31:08 +01:00
parent 03710dfdae
commit 9a1ff79f66
43 changed files with 264 additions and 92 deletions

View file

@ -1,6 +1,10 @@
<template>
<div class="relative w-full md:max-w-md">
<TrashIcon class="absolute top-3 right-3 w-5 h-5 cursor-pointer" @click="deleteCalendar" />
<TrashIcon
v-if="can('delete', 'club', 'calendar')"
class="absolute top-3 right-3 w-5 h-5 cursor-pointer"
@click="deleteCalendar"
/>
<div class="flex flex-col items-center">
<p class="text-xl font-medium">Termintyp erstellen</p>
</div>
@ -191,6 +195,7 @@ import { useCalendarTypeStore } from "@/stores/admin/calendarType";
import type { CalendarTypeViewModel } from "@/viewmodels/admin/calendarType.models";
import cloneDeep from "lodash.clonedeep";
import isEqual from "lodash.isEqual";
import { useAbilityStore } from "@/stores/ability";
</script>
<script lang="ts">
@ -208,6 +213,7 @@ export default defineComponent({
...mapState(useModalStore, ["data"]),
...mapState(useCalendarStore, ["calendars"]),
...mapState(useCalendarTypeStore, ["calendarTypes"]),
...mapState(useAbilityStore, ["can"]),
canSaveOrReset(): boolean {
return isEqual(this.origin, this.calendar);
},

View file

@ -2,8 +2,8 @@
<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">{{ award.award }}</p>
<PencilIcon class="w-5 h-5 cursor-pointer" @click="openEditModal" />
<TrashIcon class="w-5 h-5 cursor-pointer" @click="openDeleteModal" />
<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>erhalten am: {{ award.date }}</p>
@ -19,6 +19,7 @@ import { mapState, mapActions } from "pinia";
import type { MemberAwardViewModel } from "@/viewmodels/admin/memberAward.models";
import { PencilIcon, TrashIcon } from "@heroicons/vue/24/outline";
import { useModalStore } from "@/stores/modal";
import { useAbilityStore } from "@/stores/ability";
</script>
<script lang="ts">
@ -29,6 +30,9 @@ export default defineComponent({
default: {},
},
},
computed: {
...mapState(useAbilityStore, ["can"]),
},
methods: {
...mapActions(useModalStore, ["openModal"]),
openEditModal() {

View file

@ -3,8 +3,8 @@
<div class="bg-primary p-2 text-white flex flex-row gap-2 justify-between items-center">
<EnvelopeIcon class="h-5 w-5 pr-1 box-content" v-if="communication.isNewsletterMain" />
<p class="grow">{{ communication.type.type }} {{ communication.preferred ? "(bevorzugt)" : "" }}</p>
<PencilIcon class="w-5 h-5 cursor-pointer" @click="openEditModal" />
<TrashIcon class="w-5 h-5 cursor-pointer" @click="openDeleteModal" />
<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 v-for="field in communication.type.fields" :key="field">{{ field }}: {{ communication[field] || "--" }}</p>
@ -18,6 +18,7 @@ import { mapState, mapActions } from "pinia";
import type { CommunicationViewModel } from "@/viewmodels/admin/communication.models";
import { EnvelopeIcon, PencilIcon, TrashIcon } from "@heroicons/vue/24/outline";
import { useModalStore } from "@/stores/modal";
import { useAbilityStore } from "@/stores/ability";
</script>
<script lang="ts">
@ -28,6 +29,9 @@ export default defineComponent({
default: {},
},
},
computed: {
...mapState(useAbilityStore, ["can"]),
},
methods: {
...mapActions(useModalStore, ["openModal"]),
openEditModal() {

View file

@ -2,8 +2,8 @@
<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">{{ position.executivePosition }} von {{ position.start }} bis {{ position.end ?? "heute" }}</p>
<PencilIcon class="w-5 h-5 cursor-pointer" @click="openEditModal" />
<TrashIcon class="w-5 h-5 cursor-pointer" @click="openDeleteModal" />
<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 v-if="position.note" class="p-2">
<p v-if="position.note">Notiz: {{ position.note }}</p>
@ -17,6 +17,7 @@ import { mapState, mapActions } from "pinia";
import type { MemberExecutivePositionViewModel } from "@/viewmodels/admin/memberExecutivePosition.models";
import { PencilIcon, TrashIcon } from "@heroicons/vue/24/outline";
import { useModalStore } from "@/stores/modal";
import { useAbilityStore } from "@/stores/ability";
</script>
<script lang="ts">
@ -27,6 +28,9 @@ export default defineComponent({
default: {},
},
},
computed: {
...mapState(useAbilityStore, ["can"]),
},
methods: {
...mapActions(useModalStore, ["openModal"]),
openEditModal() {

View file

@ -4,8 +4,8 @@
<p class="grow">
{{ qualification.qualification }} von {{ qualification.start }} bis {{ qualification.end ?? "heute" }}
</p>
<PencilIcon class="w-5 h-5 cursor-pointer" @click="openEditModal" />
<TrashIcon class="w-5 h-5 cursor-pointer" @click="openDeleteModal" />
<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 v-if="qualification.note || qualification.terminationReason" class="p-2">
<p v-if="qualification.note">Notiz: {{ qualification.note }}</p>
@ -20,6 +20,7 @@ import { mapState, mapActions } from "pinia";
import type { MemberQualificationViewModel } from "@/viewmodels/admin/memberQualification.models";
import { PencilIcon, TrashIcon } from "@heroicons/vue/24/outline";
import { useModalStore } from "@/stores/modal";
import { useAbilityStore } from "@/stores/ability";
</script>
<script lang="ts">
@ -30,6 +31,9 @@ export default defineComponent({
default: {},
},
},
computed: {
...mapState(useAbilityStore, ["can"]),
},
methods: {
...mapActions(useModalStore, ["openModal"]),
openEditModal() {

View file

@ -5,8 +5,8 @@
{{ membership.start }} bis {{ membership.end ?? "heute" }}:
{{ membership.status }}
</p>
<PencilIcon class="w-5 h-5 cursor-pointer" @click="openEditModal" />
<TrashIcon class="w-5 h-5 cursor-pointer" @click="openDeleteModal" />
<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 v-if="membership.terminationReason || membership.internalId" class="p-2">
<p v-if="membership.internalId">Interne ID: {{ membership.internalId }}</p>
@ -21,6 +21,7 @@ import { mapState, mapActions } from "pinia";
import type { MembershipViewModel } from "@/viewmodels/admin/membership.models";
import { PencilIcon, TrashIcon } from "@heroicons/vue/24/outline";
import { useModalStore } from "@/stores/modal";
import { useAbilityStore } from "@/stores/ability";
</script>
<script lang="ts">
@ -31,6 +32,9 @@ export default defineComponent({
default: {},
},
},
computed: {
...mapState(useAbilityStore, ["can"]),
},
methods: {
...mapActions(useModalStore, ["openModal"]),
openEditModal() {

View file

@ -15,7 +15,7 @@
>
<PencilIcon class="w-5 h-5 p-1 box-content cursor-pointer" />
</RouterLink>
<div v-if="can('delete', 'user', 'user')" @click="openDeleteModal">
<div v-if="can('delete', 'user', 'role')" @click="openDeleteModal">
<TrashIcon class="w-5 h-5 p-1 box-content cursor-pointer" />
</div>
</div>

View file

@ -45,7 +45,8 @@ import { useModalStore } from "@/stores/modal";
import Spinner from "@/components/Spinner.vue";
import SuccessCheckmark from "@/components/SuccessCheckmark.vue";
import FailureXMark from "@/components/FailureXMark.vue";
import { useUserStore } from "@/stores/admin/user";
import { useInviteStore } from "@/stores/admin/invite";
import type { CreateInviteViewModel } from "@/viewmodels/admin/invite.models";
</script>
<script lang="ts">
@ -66,16 +67,17 @@ export default defineComponent({
},
methods: {
...mapActions(useModalStore, ["closeModal"]),
...mapActions(useInviteStore, ["createInvite"]),
invite(e: any) {
let formData = e.target.elements;
this.status = "loading";
this.$http
.post(`/admin/invite`, {
username: formData.username.value,
mail: formData.mail.value,
firstname: formData.firstname.value,
lastname: formData.lastname.value,
})
let createInvite: CreateInviteViewModel = {
username: formData.username.value,
mail: formData.mail.value,
firstname: formData.firstname.value,
lastname: formData.lastname.value,
};
this.createInvite(createInvite)
.then((result) => {
this.status = { status: "success" };
})

View file

@ -13,7 +13,7 @@
<UserGroupIcon class="w-5 h-5 p-1 box-content cursor-pointer" />
</RouterLink>
<RouterLink
v-if="can('admin', 'user') && false"
v-if="can('admin', 'user', 'user')"
:to="{ name: 'admin-user-user-permission', params: { id: user.id } }"
>
<WrenchScrewdriverIcon class="w-5 h-5 p-1 box-content cursor-pointer" />

View file

@ -41,20 +41,20 @@ const router = createRouter({
],
},
{
path: "/setup",
name: "setup",
path: "/reset",
name: "reset",
component: () => import("@/views/RouterView.vue"),
beforeEnter: [isSetup],
children: [
{
path: "",
name: "setup-create",
component: () => import("@/views/setup/Setup.vue"),
name: "reset-start",
component: () => import("@/views/reset/Start.vue"),
},
{
path: "verify",
name: "setup-verify",
component: () => import("@/views/setup/Verify.vue"),
path: "reset",
name: "reset-reset",
component: () => import("@/views/reset/Reset.vue"),
props: (route) => ({ mail: route.query.mail, token: route.query.token }),
},
],
@ -107,56 +107,56 @@ const router = createRouter({
{
path: "",
name: "admin-club-member",
component: () => import("@/views/admin/members/Member.vue"),
component: () => import("@/views/admin/club/members/Member.vue"),
beforeEnter: [resetMemberStores],
},
{
path: ":memberId",
name: "admin-club-member-routing",
component: () => import("@/views/admin/members/MemberRouting.vue"),
component: () => import("@/views/admin/club/members/MemberRouting.vue"),
beforeEnter: [setMemberId],
props: true,
children: [
{
path: "overview",
name: "admin-club-member-overview",
component: () => import("@/views/admin/members/MemberOverview.vue"),
component: () => import("@/views/admin/club/members/MemberOverview.vue"),
props: true,
},
{
path: "membership",
name: "admin-club-member-membership",
component: () => import("@/views/admin/members/Membership.vue"),
component: () => import("@/views/admin/club/members/Membership.vue"),
props: true,
},
{
path: "communication",
name: "admin-club-member-communication",
component: () => import("@/views/admin/members/MemberCommunication.vue"),
component: () => import("@/views/admin/club/members/MemberCommunication.vue"),
props: true,
},
{
path: "awards",
name: "admin-club-member-awards",
component: () => import("@/views/admin/members/MemberAwards.vue"),
component: () => import("@/views/admin/club/members/MemberAwards.vue"),
props: true,
},
{
path: "qualifications",
name: "admin-club-member-qualifications",
component: () => import("@/views/admin/members/MemberQualifications.vue"),
component: () => import("@/views/admin/club/members/MemberQualifications.vue"),
props: true,
},
{
path: "positions",
name: "admin-club-member-positions",
component: () => import("@/views/admin/members/MemberExecutivePositions.vue"),
component: () => import("@/views/admin/club/members/MemberExecutivePositions.vue"),
props: true,
},
{
path: "edit",
name: "admin-club-member-edit",
component: () => import("@/views/admin/members/MemberEdit.vue"),
component: () => import("@/views/admin/club/members/MemberEdit.vue"),
meta: { type: "update", section: "club", module: "member" },
beforeEnter: [abilityAndNavUpdate],
props: true,
@ -175,7 +175,7 @@ const router = createRouter({
{
path: "newsletter",
name: "admin-club-newsletter",
component: () => import("@/views/admin/members/Overview.vue"),
component: () => import("@/views/admin/ViewSelect.vue"),
meta: { type: "read", section: "club", module: "newsletter" },
beforeEnter: [abilityAndNavUpdate],
},

View file

@ -30,7 +30,7 @@ export const useCalendarStore = defineStore("calendar", {
fetchCalendars() {
this.loading = "loading";
http
.get("/admin/calendar/items")
.get("/admin/calendar")
.then((result) => {
this.calendars = result.data;
this.loading = "fetched";
@ -40,10 +40,10 @@ export const useCalendarStore = defineStore("calendar", {
});
},
fetchCalendarById(id: string): Promise<AxiosResponse<any, any>> {
return http.get(`/admin/calendar/item/${id}`);
return http.get(`/admin/calendar/${id}`);
},
async createCalendar(calendar: CreateCalendarViewModel): Promise<AxiosResponse<any, any>> {
const result = await http.post(`/admin/calendar/item`, {
const result = await http.post(`/admin/calendar`, {
starttime: calendar.starttime,
endtime: calendar.endtime,
title: calendar.title,
@ -56,7 +56,7 @@ export const useCalendarStore = defineStore("calendar", {
return result;
},
async updateCalendar(calendar: UpdateCalendarViewModel): Promise<AxiosResponse<any, any>> {
const result = await http.patch(`/admin/calendar/item/${calendar.id}`, {
const result = await http.patch(`/admin/calendar/${calendar.id}`, {
starttime: calendar.starttime,
endtime: calendar.endtime,
title: calendar.title,
@ -69,7 +69,7 @@ export const useCalendarStore = defineStore("calendar", {
return result;
},
async deleteCalendar(calendar: number): Promise<AxiosResponse<any, any>> {
const result = await http.delete(`/admin/calendar/item/${calendar}`);
const result = await http.delete(`/admin/calendar/${calendar}`);
this.fetchCalendars();
return result;
},

View file

@ -19,7 +19,7 @@ export const useCalendarTypeStore = defineStore("calendarType", {
fetchCalendarTypes() {
this.loading = "loading";
http
.get("/admin/calendar/types")
.get("/admin/calendartype")
.then((result) => {
this.calendarTypes = result.data;
this.loading = "fetched";
@ -29,10 +29,10 @@ export const useCalendarTypeStore = defineStore("calendarType", {
});
},
fetchCalendarTypeById(id: number): Promise<AxiosResponse<any, any>> {
return http.get(`/admin/calendar/type/${id}`);
return http.get(`/admin/calendartype/${id}`);
},
async createCalendarType(calendarType: CreateCalendarTypeViewModel): Promise<AxiosResponse<any, any>> {
const result = await http.post(`/admin/calendar/type`, {
const result = await http.post(`/admin/calendartype`, {
type: calendarType.type,
nscdr: calendarType.nscdr,
color: calendarType.color,
@ -41,7 +41,7 @@ export const useCalendarTypeStore = defineStore("calendarType", {
return result;
},
async updateActiveCalendarType(calendarType: UpdateCalendarTypeViewModel): Promise<AxiosResponse<any, any>> {
const result = await http.patch(`/admin/calendar/type/${calendarType.id}`, {
const result = await http.patch(`/admin/calendartype/${calendarType.id}`, {
type: calendarType.type,
nscdr: calendarType.nscdr,
color: calendarType.color,
@ -50,7 +50,7 @@ export const useCalendarTypeStore = defineStore("calendarType", {
return result;
},
async deleteCalendarType(calendarType: number): Promise<AxiosResponse<any, any>> {
const result = await http.delete(`/admin/calendar/type/${calendarType}`);
const result = await http.delete(`/admin/calendartype/${calendarType}`);
this.fetchCalendarTypes();
return result;
},

View file

@ -1,5 +1,5 @@
import { defineStore } from "pinia";
import type { InviteViewModel } from "@/viewmodels/admin/invite.models";
import type { CreateInviteViewModel, InviteViewModel } from "@/viewmodels/admin/invite.models";
import { http } from "@/serverCom";
import type { PermissionObject } from "@/types/permissionTypes";
import type { AxiosResponse } from "axios";
@ -24,11 +24,18 @@ export const useInviteStore = defineStore("invite", {
this.loading = "failed";
});
},
deleteInvite(mail: string): Promise<AxiosResponse<any, any>> {
return http.delete(`/admin/invite/${mail}`).then((result) => {
this.fetchInvites();
return result;
createInvite(createInvite: CreateInviteViewModel): Promise<AxiosResponse<any, any>> {
return http.post(`/admin/invite`, {
username: createInvite.username,
mail: createInvite.mail,
firstname: createInvite.firstname,
lastname: createInvite.lastname,
});
},
async deleteInvite(mail: string): Promise<AxiosResponse<any, any>> {
const result = await http.delete(`/admin/invite/${mail}`);
this.fetchInvites();
return result;
},
},
});

View file

@ -1,5 +1,11 @@
export interface InviteViewModel {
id: number;
username: string;
mail: string;
firstname: string;
lastname: string;
}
export interface CreateInviteViewModel {
username: string;
mail: string;
firstname: string;

View file

@ -23,7 +23,8 @@ import deLocale from "@fullcalendar/core/locales/de";
import dayGridPlugin from "@fullcalendar/daygrid";
import timeGridPlugin from "@fullcalendar/timegrid";
import interactionPlugin from "@fullcalendar/interaction";
import { useCalendarStore } from "../../../../stores/admin/calendar";
import { useCalendarStore } from "@/stores/admin/calendar";
import { useAbilityStore } from "@/stores/ability";
</script>
<script lang="ts">
@ -33,6 +34,7 @@ export default defineComponent({
},
computed: {
...mapState(useCalendarStore, ["formattedItems"]),
...mapState(useAbilityStore, ["can"]),
calendarOptions() {
return {
timeZone: "local",
@ -68,7 +70,7 @@ export default defineComponent({
...mapActions(useModalStore, ["openModal"]),
...mapActions(useCalendarStore, ["fetchCalendars"]),
select(e: any) {
console.log(e);
if (!this.can("create", "club", "calendar")) return;
this.openModal(
markRaw(defineAsyncComponent(() => import("@/components/admin/club/calendar/CreateCalendarModal.vue"))),
{
@ -79,6 +81,7 @@ export default defineComponent({
);
},
eventClick(e: any) {
if (!this.can("update", "club", "calendar")) return;
this.openModal(
markRaw(defineAsyncComponent(() => import("@/components/admin/club/calendar/UpdateCalendarModal.vue"))),
e.event.id

View file

@ -20,7 +20,9 @@
</Pagination>
<div class="flex flex-row gap-4">
<button primary class="!w-fit" @click="openCreateModal">Mitglied erstellen</button>
<button v-if="can('create', 'club', 'member')" primary class="!w-fit" @click="openCreateModal">
Mitglied erstellen
</button>
</div>
</div>
</template>
@ -35,8 +37,9 @@ import { ChevronRightIcon, ChevronLeftIcon } from "@heroicons/vue/20/solid";
import { useMemberStore } from "@/stores/admin/member";
import MemberListItem from "@/components/admin/club/member/MemberListItem.vue";
import { useModalStore } from "@/stores/modal";
import Pagination from "../../../components/Pagination.vue";
import type { MemberViewModel } from "../../../viewmodels/admin/member.models";
import Pagination from "@/components/Pagination.vue";
import type { MemberViewModel } from "@/viewmodels/admin/member.models";
import { useAbilityStore } from "@/stores/ability";
</script>
<script lang="ts">
@ -49,6 +52,7 @@ export default defineComponent({
},
computed: {
...mapState(useMemberStore, ["members", "totalCount", "loading"]),
...mapState(useAbilityStore, ["can"]),
entryCount() {
return this.totalCount ?? this.members.length;
},

View file

@ -7,7 +7,9 @@
<p v-else-if="loading == 'failed'" @click="fetchItem" class="cursor-pointer">&#8634; laden fehlgeschlagen</p>
</div>
<div class="flex flex-row gap-4">
<button primary class="!w-fit" @click="openCreateModal">Auszeichnung hinzufügen</button>
<button v-if="can('create', 'club', 'member')" primary class="!w-fit" @click="openCreateModal">
Auszeichnung hinzufügen
</button>
</div>
</template>
@ -18,6 +20,7 @@ import Spinner from "@/components/Spinner.vue";
import { useMemberAwardStore } from "@/stores/admin/memberAward";
import MemberAwardListItem from "@/components/admin/club/member/MemberAwardListItem.vue";
import { useModalStore } from "@/stores/modal";
import { useAbilityStore } from "@/stores/ability";
</script>
<script lang="ts">
@ -27,6 +30,7 @@ export default defineComponent({
},
computed: {
...mapState(useMemberAwardStore, ["memberAwards", "loading"]),
...mapState(useAbilityStore, ["can"]),
},
mounted() {
this.fetchItem();

View file

@ -11,7 +11,9 @@
<p v-else-if="loading == 'failed'" @click="fetchItem" class="cursor-pointer">&#8634; laden fehlgeschlagen</p>
</div>
<div class="flex flex-row gap-4">
<button primary class="!w-fit" @click="openCreateModal">Kommunikation hinzufügen</button>
<button v-if="can('create', 'club', 'member')" primary class="!w-fit" @click="openCreateModal">
Kommunikation hinzufügen
</button>
</div>
</template>
@ -25,6 +27,7 @@ import type { CommunicationViewModel } from "@/viewmodels/admin/communication.mo
import { useCommunicationStore } from "@/stores/admin/communication";
import MemberCommunicationListItem from "@/components/admin/club/member/MemberCommunicationListItem.vue";
import { useModalStore } from "@/stores/modal";
import { useAbilityStore } from "@/stores/ability";
</script>
<script lang="ts">
@ -34,6 +37,7 @@ export default defineComponent({
},
computed: {
...mapState(useCommunicationStore, ["communications", "loading"]),
...mapState(useAbilityStore, ["can"]),
},
mounted() {
this.fetchItem();

View file

@ -11,7 +11,9 @@
<p v-else-if="loading == 'failed'" @click="fetchItem" class="cursor-pointer">&#8634; laden fehlgeschlagen</p>
</div>
<div class="flex flex-row gap-4">
<button primary class="!w-fit" @click="openCreateModal">Vereinsamt hinzufügen</button>
<button v-if="can('create', 'club', 'member')" primary class="!w-fit" @click="openCreateModal">
Vereinsamt hinzufügen
</button>
</div>
</template>
@ -22,6 +24,7 @@ import Spinner from "@/components/Spinner.vue";
import { useMemberExecutivePositionStore } from "@/stores/admin/memberExecutivePosition";
import MemberExecutivePositionListItem from "@/components/admin/club/member/MemberExecutivePositionListItem.vue";
import { useModalStore } from "@/stores/modal";
import { useAbilityStore } from "@/stores/ability";
</script>
<script lang="ts">
@ -31,6 +34,7 @@ export default defineComponent({
},
computed: {
...mapState(useMemberExecutivePositionStore, ["memberExecutivePositions", "loading"]),
...mapState(useAbilityStore, ["can"]),
},
mounted() {
this.fetchItem();

View file

@ -11,7 +11,9 @@
<p v-else-if="loading == 'failed'" @click="fetchItem" class="cursor-pointer">&#8634; laden fehlgeschlagen</p>
</div>
<div class="flex flex-row gap-4">
<button primary class="!w-fit" @click="openCreateModal">Qualifikation hinzufügen</button>
<button v-if="can('create', 'club', 'member')" primary class="!w-fit" @click="openCreateModal">
Qualifikation hinzufügen
</button>
</div>
</template>
@ -22,6 +24,7 @@ import Spinner from "@/components/Spinner.vue";
import { useMemberQualificationStore } from "@/stores/admin/memberQualification";
import MemberQualificationListItem from "@/components/admin/club/member/MemberQualificationListItem.vue";
import { useModalStore } from "@/stores/modal";
import { useAbilityStore } from "@/stores/ability";
</script>
<script lang="ts">
@ -31,6 +34,7 @@ export default defineComponent({
},
computed: {
...mapState(useMemberQualificationStore, ["memberQualifications", "loading"]),
...mapState(useAbilityStore, ["can"]),
},
mounted() {
this.fetchItem();

View file

@ -9,10 +9,10 @@
{{ activeMemberObj?.lastname }}, {{ activeMemberObj?.firstname }}
{{ activeMemberObj?.nameaffix ? `- ${activeMemberObj?.nameaffix}` : "" }}
</h1>
<RouterLink :to="{ name: 'admin-club-member-edit' }">
<RouterLink v-if="can('update', 'club', 'member')" :to="{ name: 'admin-club-member-edit' }">
<PencilIcon class="w-5 h-5" />
</RouterLink>
<TrashIcon class="w-5 h-5 cursor-pointer" @click="openDeleteModal" />
<TrashIcon v-if="can('delete', 'club', 'member')" class="w-5 h-5 cursor-pointer" @click="openDeleteModal" />
</div>
</template>
<template #diffMain>
@ -51,6 +51,7 @@ import { RouterLink, RouterView } from "vue-router";
import { useMemberStore } from "@/stores/admin/member";
import { PencilIcon, TrashIcon } from "@heroicons/vue/24/outline";
import { useModalStore } from "@/stores/modal";
import { useAbilityStore } from "@/stores/ability";
</script>
<script lang="ts">
@ -72,6 +73,7 @@ export default defineComponent({
},
computed: {
...mapState(useMemberStore, ["activeMemberObj"]),
...mapState(useAbilityStore, ["can"]),
},
mounted() {
this.fetchMemberByActiveId();

View file

@ -7,7 +7,9 @@
<p v-else-if="loading == 'failed'" @click="fetchItem" class="cursor-pointer">&#8634; laden fehlgeschlagen</p>
</div>
<div class="flex flex-row gap-4">
<button primary class="!w-fit" @click="openCreateModal">Mitgliedschaft hinzufügen</button>
<button v-if="can('create', 'club', 'member')" primary class="!w-fit" @click="openCreateModal">
Mitgliedschaft hinzufügen
</button>
</div>
</template>
@ -18,6 +20,7 @@ import Spinner from "@/components/Spinner.vue";
import { useMembershipStore } from "@/stores/admin/membership";
import { useModalStore } from "@/stores/modal";
import MembershipListItem from "@/components/admin/club/member/MembershipListItem.vue";
import { useAbilityStore } from "@/stores/ability";
</script>
<script lang="ts">
@ -27,6 +30,7 @@ export default defineComponent({
},
computed: {
...mapState(useMembershipStore, ["memberships", "loading"]),
...mapState(useAbilityStore, ["can"]),
},
mounted() {
this.fetchItem();

View file

@ -20,7 +20,9 @@
</Pagination>
<div class="flex flex-row gap-4">
<button primary class="!w-fit" @click="openCreateModal">Protokoll erstellen</button>
<button v-if="can('create', 'club', 'protocol')" primary class="!w-fit" @click="openCreateModal">
Protokoll erstellen
</button>
</div>
</div>
</template>
@ -35,8 +37,9 @@ import { ChevronRightIcon, ChevronLeftIcon } from "@heroicons/vue/20/solid";
import { useProtocolStore } from "@/stores/admin/protocol";
import ProtocolListItem from "@/components/admin/club/protocol/ProtocolListItem.vue";
import { useModalStore } from "@/stores/modal";
import Pagination from "../../../../components/Pagination.vue";
import type { ProtocolViewModel } from "../../../../viewmodels/admin/protocol.models";
import Pagination from "@/components/Pagination.vue";
import type { ProtocolViewModel } from "@/viewmodels/admin/protocol.models";
import { useAbilityStore } from "@/stores/ability";
</script>
<script lang="ts">
@ -49,6 +52,7 @@ export default defineComponent({
},
computed: {
...mapState(useProtocolStore, ["protocols", "totalCount", "loading"]),
...mapState(useAbilityStore, ["can"]),
},
mounted() {
this.fetchProtocols(0, this.maxEntriesPerPage, true);

View file

@ -27,6 +27,7 @@
autocomplete="off"
v-model="item.topic"
@keyup.prevent
:disabled="!can('create', 'club', 'protocol')"
/>
</summary>
<QuillEditor
@ -37,11 +38,15 @@
contentType="html"
:toolbar="toolbarOptions"
v-model:content="item.context"
:enable="can('create', 'club', 'protocol')"
:style="!can('create', 'club', 'protocol') ? 'opacity: 75%; background: rgb(243 244 246)' : ''"
/>
</details>
</div>
<button primary class="!w-fit" @click="createProtocolAgenda">Eintrag hinzufügen</button>
<button v-if="can('create', 'club', 'protocol')" primary class="!w-fit" @click="createProtocolAgenda">
Eintrag hinzufügen
</button>
</div>
</template>
@ -54,6 +59,7 @@ import "@vueup/vue-quill/dist/vue-quill.snow.css";
import { toolbarOptions } from "@/helpers/quillConfig";
import { useProtocolAgendaStore } from "@/stores/admin/protocolAgenda";
import type { ProtocolAgendaViewModel } from "@/viewmodels/admin/protocolAgenda.models";
import { useAbilityStore } from "@/stores/ability";
</script>
<script lang="ts">
@ -63,6 +69,7 @@ export default defineComponent({
},
computed: {
...mapWritableState(useProtocolAgendaStore, ["agenda", "loading"]),
...mapState(useAbilityStore, ["can"]),
},
mounted() {
this.fetchProtocolAgenda();

View file

@ -27,6 +27,7 @@
autocomplete="off"
v-model="item.topic"
@keyup.prevent
:disabled="!can('create', 'club', 'protocol')"
/>
</summary>
<QuillEditor
@ -37,11 +38,15 @@
contentType="html"
:toolbar="toolbarOptions"
v-model:content="item.context"
:enable="can('create', 'club', 'protocol')"
:style="!can('create', 'club', 'protocol') ? 'opacity: 75%; background: rgb(243 244 246)' : ''"
/>
</details>
</div>
<button primary class="!w-fit" @click="createProtocolDecision">Eintrag hinzufügen</button>
<button v-if="can('create', 'club', 'protocol')" primary class="!w-fit" @click="createProtocolDecision">
Eintrag hinzufügen
</button>
</div>
</template>
@ -53,7 +58,8 @@ import { useProtocolStore } from "@/stores/admin/protocol";
import { QuillEditor } from "@vueup/vue-quill";
import "@vueup/vue-quill/dist/vue-quill.snow.css";
import { toolbarOptions } from "@/helpers/quillConfig";
import { useProtocolDecisionStore } from "../../../../stores/admin/protocolDecision";
import { useProtocolDecisionStore } from "@/stores/admin/protocolDecision";
import { useAbilityStore } from "@/stores/ability";
</script>
<script lang="ts">
@ -63,6 +69,7 @@ export default defineComponent({
},
computed: {
...mapWritableState(useProtocolDecisionStore, ["decision", "loading"]),
...mapState(useAbilityStore, ["can"]),
},
mounted() {
this.fetchProtocolDecision();

View file

@ -3,20 +3,37 @@
<div v-if="activeProtocolObj != null" class="flex flex-col gap-2 w-full">
<div class="w-full">
<label for="title">Titel</label>
<input type="text" id="title" v-model="activeProtocolObj.title" />
<input
type="text"
id="title"
v-model="activeProtocolObj.title"
:disabled="!can('create', 'club', 'protocol')"
/>
</div>
<div class="w-full">
<label for="date">Datum</label>
<input type="date" id="date" v-model="activeProtocolObj.date" />
<input type="date" id="date" v-model="activeProtocolObj.date" :disabled="!can('create', 'club', 'protocol')" />
</div>
<div class="flex flex-row gap-2 w-full">
<div class="w-full">
<label for="starttime">Startzeit</label>
<input type="time" id="starttime" step="1" v-model="activeProtocolObj.starttime" />
<input
type="time"
id="starttime"
step="1"
v-model="activeProtocolObj.starttime"
:disabled="!can('create', 'club', 'protocol')"
/>
</div>
<div class="w-full">
<label for="endtime">Endzeit</label>
<input type="time" id="endtime" step="1" v-model="activeProtocolObj.endtime" />
<input
type="time"
id="endtime"
step="1"
v-model="activeProtocolObj.endtime"
:disabled="!can('create', 'club', 'protocol')"
/>
</div>
</div>
<div class="flex flex-col h-1/2">
@ -29,6 +46,8 @@
contentType="html"
:toolbar="toolbarOptions"
v-model:content="activeProtocolObj.summary"
:enable="can('create', 'club', 'protocol')"
:style="!can('create', 'club', 'protocol') ? 'opacity: 75%; background: rgb(243 244 246)' : ''"
/>
</div>
</div>
@ -48,6 +67,7 @@ import { useProtocolStore } from "@/stores/admin/protocol";
import { QuillEditor } from "@vueup/vue-quill";
import "@vueup/vue-quill/dist/vue-quill.snow.css";
import { toolbarOptions } from "@/helpers/quillConfig";
import { useAbilityStore } from "@/stores/ability";
</script>
<script lang="ts">
@ -57,6 +77,7 @@ export default defineComponent({
},
computed: {
...mapWritableState(useProtocolStore, ["loadingActive", "activeProtocolObj"]),
...mapState(useAbilityStore, ["can"]),
},
mounted() {
this.fetchProtocolByActiveId();

View file

@ -6,7 +6,7 @@
</p>
<div class="w-full">
<Combobox v-model="presence" multiple>
<Combobox v-model="presence" :disabled="!can('create', 'club', 'protocol')" multiple>
<ComboboxLabel>Anwesende suchen</ComboboxLabel>
<div class="relative mt-1">
<ComboboxInput
@ -71,7 +71,11 @@
class="flex flex-row h-fit w-full border border-primary rounded-md bg-primary p-2 text-white justify-between items-center"
>
<p>{{ member.lastname }}, {{ member.firstname }} {{ member.nameaffix ? `- ${member.nameaffix}` : "" }}</p>
<TrashIcon class="w-5 h-5 p-1 box-content cursor-pointer" @click="removeSelected(member.id)" />
<TrashIcon
v-if="can('create', 'club', 'protocol')"
class="w-5 h-5 p-1 box-content cursor-pointer"
@click="removeSelected(member.id)"
/>
</div>
</div>
</div>
@ -96,6 +100,7 @@ import { useProtocolStore } from "@/stores/admin/protocol";
import { useMemberStore } from "@/stores/admin/member";
import type { MemberViewModel } from "@/viewmodels/admin/member.models";
import { useProtocolPresenceStore } from "@/stores/admin/protocolPresence";
import { useAbilityStore } from "@/stores/ability";
</script>
<script lang="ts">
@ -111,6 +116,7 @@ export default defineComponent({
computed: {
...mapWritableState(useProtocolPresenceStore, ["presence", "loading"]),
...mapState(useMemberStore, ["members"]),
...mapState(useAbilityStore, ["can"]),
filtered(): Array<MemberViewModel> {
return this.query === ""
? this.members

View file

@ -30,7 +30,13 @@
</div>
<div class="flex flex-row justify-start gap-2">
<button primary class="!w-fit" :disabled="printing != undefined" @click="createProtocolPrintout">
<button
v-if="can('create', 'club', 'protocol')"
primary
class="!w-fit"
:disabled="printing != undefined"
@click="createProtocolPrintout"
>
Ausdruck erstellen
</button>
<Spinner v-if="printing == 'loading'" class="my-auto" />
@ -49,6 +55,7 @@ import FailureXMark from "@/components/FailureXMark.vue";
import { useProtocolPrintoutStore } from "@/stores/admin/protocolPrintout";
import { ArrowDownTrayIcon, ViewfinderCircleIcon } from "@heroicons/vue/24/outline";
import { useModalStore } from "@/stores/modal";
import { useAbilityStore } from "@/stores/ability";
</script>
<script lang="ts">
@ -58,6 +65,7 @@ export default defineComponent({
},
computed: {
...mapState(useProtocolPrintoutStore, ["printout", "loading", "printing"]),
...mapState(useAbilityStore, ["can"]),
},
mounted() {
this.fetchProtocolPrintout();

View file

@ -14,7 +14,6 @@
}
"
/>
<!-- <TrashIcon class="w-5 h-5 cursor-pointer" @click="openDeleteModal" /> -->
</div>
</template>
<template #diffMain>

View file

@ -27,6 +27,7 @@
autocomplete="off"
v-model="item.topic"
@keyup.prevent
:disabled="!can('create', 'club', 'protocol')"
/>
</summary>
<QuillEditor
@ -37,6 +38,8 @@
contentType="html"
:toolbar="toolbarOptions"
v-model:content="item.context"
:enable="can('create', 'club', 'protocol')"
:style="!can('create', 'club', 'protocol') ? 'opacity: 75%; background: rgb(243 244 246)' : ''"
/>
<div class="px-2 pb-2">
<p>Ergebnis:</p>
@ -58,7 +61,9 @@
</details>
</div>
<button primary class="!w-fit" @click="createProtocolVoting">Abstimmung hinzufügen</button>
<button v-if="can('create', 'club', 'protocol')" primary class="!w-fit" @click="createProtocolVoting">
Abstimmung hinzufügen
</button>
</div>
</template>
@ -70,7 +75,8 @@ import { useProtocolStore } from "@/stores/admin/protocol";
import { QuillEditor } from "@vueup/vue-quill";
import "@vueup/vue-quill/dist/vue-quill.snow.css";
import { toolbarOptions } from "@/helpers/quillConfig";
import { useProtocolVotingStore } from "../../../../stores/admin/protocolVoting";
import { useProtocolVotingStore } from "@/stores/admin/protocolVoting";
import { useAbilityStore } from "@/stores/ability";
</script>
<script lang="ts">
@ -80,6 +86,7 @@ export default defineComponent({
},
computed: {
...mapState(useProtocolVotingStore, ["voting", "loading"]),
...mapState(useAbilityStore, ["can"]),
},
mounted() {
this.fetchProtocolVoting();

View file

@ -11,7 +11,9 @@
<AwardListItem v-for="award in awards" :key="award.id" :award="award" />
</div>
<div class="flex flex-row gap-4">
<button primary class="!w-fit" @click="openCreateModal">Auszeichnung erstellen</button>
<button v-if="can('create', 'settings', 'award')" primary class="!w-fit" @click="openCreateModal">
Auszeichnung erstellen
</button>
</div>
</div>
</template>
@ -25,12 +27,14 @@ import MainTemplate from "@/templates/Main.vue";
import { useAwardStore } from "@/stores/admin/award";
import AwardListItem from "@/components/admin/settings/award/AwardListItem.vue";
import { useModalStore } from "@/stores/modal";
import { useAbilityStore } from "@/stores/ability";
</script>
<script lang="ts">
export default defineComponent({
computed: {
...mapState(useAwardStore, ["awards"]),
...mapState(useAbilityStore, ["can"]),
},
mounted() {
this.fetchAwards();

View file

@ -15,7 +15,9 @@
/>
</div>
<div class="flex flex-row gap-4">
<button primary class="!w-fit" @click="openCreateModal">Termintyp erstellen</button>
<button v-if="can('create', 'settings', 'calendar_type')" primary class="!w-fit" @click="openCreateModal">
Termintyp erstellen
</button>
</div>
</div>
</template>
@ -29,12 +31,14 @@ import MainTemplate from "@/templates/Main.vue";
import { useCalendarTypeStore } from "@/stores/admin/calendarType";
import CalendarTypeListItem from "@/components/admin/settings/calendarType/CalendarTypeListItem.vue";
import { useModalStore } from "@/stores/modal";
import { useAbilityStore } from "@/stores/ability";
</script>
<script lang="ts">
export default defineComponent({
computed: {
...mapState(useCalendarTypeStore, ["calendarTypes"]),
...mapState(useAbilityStore, ["can"]),
},
mounted() {
this.fetchCalendarTypes();

View file

@ -15,7 +15,14 @@
/>
</div>
<div class="flex flex-row gap-4">
<button primary class="!w-fit" @click="openCreateModal">Kommunikationsart erstellen</button>
<button
v-if="can('create', 'settings', 'communication_type')"
primary
class="!w-fit"
@click="openCreateModal"
>
Kommunikationsart erstellen
</button>
</div>
</div>
</template>
@ -29,12 +36,14 @@ import MainTemplate from "@/templates/Main.vue";
import { useCommunicationTypeStore } from "@/stores/admin/communicationType";
import CommunicationTypeListItem from "@/components/admin/settings/communicationType/CommunicationTypeListItem.vue";
import { useModalStore } from "@/stores/modal";
import { useAbilityStore } from "@/stores/ability";
</script>
<script lang="ts">
export default defineComponent({
computed: {
...mapState(useCommunicationTypeStore, ["communicationTypes"]),
...mapState(useAbilityStore, ["can"]),
},
mounted() {
this.fetchCommunicationTypes();

View file

@ -15,7 +15,14 @@
/>
</div>
<div class="flex flex-row gap-4">
<button primary class="!w-fit" @click="openCreateModal">Vereinsämt erstellen</button>
<button
v-if="can('create', 'settings', 'executive_position')"
primary
class="!w-fit"
@click="openCreateModal"
>
Vereinsämt erstellen
</button>
</div>
</div>
</template>
@ -29,12 +36,14 @@ import MainTemplate from "@/templates/Main.vue";
import { useExecutivePositionStore } from "@/stores/admin/executivePosition";
import ExecutivePositionListItem from "@/components/admin/settings/executivePosition/ExecutivePositionListItem.vue";
import { useModalStore } from "@/stores/modal";
import { useAbilityStore } from "@/stores/ability";
</script>
<script lang="ts">
export default defineComponent({
computed: {
...mapState(useExecutivePositionStore, ["executivePositions"]),
...mapState(useAbilityStore, ["can"]),
},
mounted() {
this.fetchExecutivePositions();

View file

@ -11,7 +11,9 @@
<MembershipStatusListItem v-for="status in membershipStatus" :key="status.id" :membershipStatus="status" />
</div>
<div class="flex flex-row gap-4">
<button primary class="!w-fit" @click="openCreateModal">Mitgliedsstatus erstellen</button>
<button v-if="can('create', 'settings', 'membership_status')" primary class="!w-fit" @click="openCreateModal">
Mitgliedsstatus erstellen
</button>
</div>
</div>
</template>
@ -25,12 +27,14 @@ import MainTemplate from "@/templates/Main.vue";
import { useMembershipStatusStore } from "@/stores/admin/membershipStatus";
import MembershipStatusListItem from "@/components/admin/settings/membershipStatus/MembershipStatusListItem.vue";
import { useModalStore } from "@/stores/modal";
import { useAbilityStore } from "@/stores/ability";
</script>
<script lang="ts">
export default defineComponent({
computed: {
...mapState(useMembershipStatusStore, ["membershipStatus"]),
...mapState(useAbilityStore, ["can"]),
},
mounted() {
this.fetchMembershipStatus();

View file

@ -15,7 +15,9 @@
/>
</div>
<div class="flex flex-row gap-4">
<button primary class="!w-fit" @click="openCreateModal">Qualifikation erstellen</button>
<button v-if="can('create', 'settings', 'qualification')" primary class="!w-fit" @click="openCreateModal">
Qualifikation erstellen
</button>
</div>
</div>
</template>
@ -29,12 +31,14 @@ import MainTemplate from "@/templates/Main.vue";
import { useQualificationStore } from "@/stores/admin/qualification";
import QualificationListItem from "@/components/admin/settings/qualification/QualificationListItem.vue";
import { useModalStore } from "@/stores/modal";
import { useAbilityStore } from "@/stores/ability";
</script>
<script lang="ts">
export default defineComponent({
computed: {
...mapState(useQualificationStore, ["qualifications"]),
...mapState(useAbilityStore, ["can"]),
},
mounted() {
this.fetchQualifications();

View file

@ -10,7 +10,7 @@
</template>
<template #diffMain>
<div class="flex flex-col gap-2 grow overflow-y-scroll px-7">
<InviteListItem v-for="invite in invites" :key="invite.id" :invite="invite" />
<InviteListItem v-for="invite in invites" :key="invite.username" :invite="invite" />
</div>
</template>
</MainTemplate>

View file

@ -11,7 +11,9 @@
<RoleListItem v-for="role in roles" :key="role.id" :role="role" />
</div>
<div class="flex flex-row gap-4">
<button primary class="!w-fit" @click="openCreateModal">Rolle erstellen</button>
<button v-if="can('create', 'user', 'role')" primary class="!w-fit" @click="openCreateModal">
Rolle erstellen
</button>
</div>
</div>
</template>
@ -25,12 +27,14 @@ import MainTemplate from "@/templates/Main.vue";
import { useRoleStore } from "@/stores/admin/role";
import RoleListItem from "@/components/admin/user/role/RoleListItem.vue";
import { useModalStore } from "@/stores/modal";
import { useAbilityStore } from "@/stores/ability";
</script>
<script lang="ts">
export default defineComponent({
computed: {
...mapState(useRoleStore, ["roles"]),
...mapState(useAbilityStore, ["can"]),
},
mounted() {
this.fetchRoles();

View file

@ -11,7 +11,9 @@
<UserListItem v-for="user in users" :key="user.id" :user="user" />
</div>
<div class="flex flex-row gap-4">
<button primary class="!w-fit" @click="inviteUser">Nutzer einladen</button>
<button v-if="can('create', 'user', 'user')" primary class="!w-fit" @click="inviteUser">
Nutzer einladen
</button>
<RouterLink button primary-outline :to="{ name: 'admin-user-user-invites' }" class="!w-fit">
offene Einladungen
</RouterLink>
@ -29,12 +31,14 @@ import MainTemplate from "@/templates/Main.vue";
import { useUserStore } from "@/stores/admin/user";
import { useModalStore } from "@/stores/modal";
import UserListItem from "@/components/admin/user/user/UserListItem.vue";
import { useAbilityStore } from "@/stores/ability";
</script>
<script lang="ts">
export default defineComponent({
computed: {
...mapState(useUserStore, ["users"]),
...mapState(useAbilityStore, ["can"]),
},
mounted() {
this.fetchUsers();

View file

@ -9,6 +9,7 @@
</div>
</template>
<template #main>
<p>Hinweis: Berechtigungen von Nutzer und Rolle sind ergänzend.</p>
<Spinner v-if="loading == 'loading'" class="mx-auto" />
<p v-else-if="loading == 'failed'">laden fehlgeschlagen</p>
<Permission