calendartypes
This commit is contained in:
parent
5c2865a9e5
commit
762a9f2b8e
12 changed files with 523 additions and 11 deletions
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<footer
|
<footer
|
||||||
v-if="authCheck && routeName.includes('admin')"
|
v-if="authCheck && routeName.includes('admin')"
|
||||||
class="md:hidden flex flex-row h-16 justify-center md:justify-normal p-1 bg-white"
|
class="md:hidden flex flex-row h-16 min-h-16 justify-center md:justify-normal p-1 bg-white"
|
||||||
>
|
>
|
||||||
<div class="w-full flex flex-row gap-2 h-full align-middle">
|
<div class="w-full flex flex-row gap-2 h-full align-middle">
|
||||||
<TopLevelLink v-for="item in topLevel" :key="item.key" :link="item" :disableSubLink="true" />
|
<TopLevelLink v-for="item in topLevel" :key="item.key" :link="item" :disableSubLink="true" />
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
<template>
|
||||||
|
<div class="flex flex-col h-fit w-full border border-primary rounded-md overflow-hidden">
|
||||||
|
<div class="bg-primary p-2 text-white flex flex-row justify-between items-center">
|
||||||
|
<div class="flex flex-row items-center gap-2">
|
||||||
|
<div class="rounded-md h-5 w-5" :style="'background-color:' + calendarType.color">
|
||||||
|
<EyeIcon v-if="calendarType.nscdr" class="w-5 h-5" />
|
||||||
|
</div>
|
||||||
|
<p>{{ calendarType.type }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-row">
|
||||||
|
<RouterLink
|
||||||
|
v-if="can('update', 'settings', 'calendar_type')"
|
||||||
|
:to="{ name: 'admin-settings-calendar_type-edit', params: { id: calendarType.id } }"
|
||||||
|
>
|
||||||
|
<PencilIcon class="w-5 h-5 p-1 box-content cursor-pointer" />
|
||||||
|
</RouterLink>
|
||||||
|
<div v-if="can('delete', 'settings', 'calendar_type')" @click="openDeleteModal">
|
||||||
|
<TrashIcon class="w-5 h-5 p-1 box-content cursor-pointer" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { defineComponent, defineAsyncComponent, markRaw, type PropType } from "vue";
|
||||||
|
import { mapState, mapActions } from "pinia";
|
||||||
|
import { PencilIcon, TrashIcon, EyeIcon } from "@heroicons/vue/24/outline";
|
||||||
|
import { useAbilityStore } from "@/stores/ability";
|
||||||
|
import { useModalStore } from "@/stores/modal";
|
||||||
|
import type { CalendarTypeViewModel } from "@/viewmodels/admin/calendarType.models";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default defineComponent({
|
||||||
|
props: {
|
||||||
|
calendarType: { type: Object as PropType<CalendarTypeViewModel>, default: {} },
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState(useAbilityStore, ["can"]),
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions(useModalStore, ["openModal"]),
|
||||||
|
openDeleteModal() {
|
||||||
|
this.openModal(
|
||||||
|
markRaw(
|
||||||
|
defineAsyncComponent(() => import("@/components/admin/settings/calendarType/DeleteCalendarTypeModal.vue"))
|
||||||
|
),
|
||||||
|
this.calendarType.id
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -0,0 +1,90 @@
|
||||||
|
<template>
|
||||||
|
<div class="w-full md:max-w-md">
|
||||||
|
<div class="flex flex-col items-center">
|
||||||
|
<p class="text-xl font-medium">Termintyp erstellen</p>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<form class="flex flex-col gap-4 py-2" @submit.prevent="triggerCreate">
|
||||||
|
<div>
|
||||||
|
<label for="type">Bezeichnung</label>
|
||||||
|
<input type="text" id="type" required />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-row items-center gap-2">
|
||||||
|
<input type="color" id="color" required class="!px-1 !py-0 !w-10" />
|
||||||
|
<label for="color">Farbe</label>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-row items-center gap-2">
|
||||||
|
<input type="checkbox" id="nscdr" />
|
||||||
|
<label for="nscdr">Standard Kalender Auslieferung (optional)</label>
|
||||||
|
</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 != null && status?.status != 'failed'">
|
||||||
|
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 { useCalendarTypeStore } from "@/stores/admin/calendarType";
|
||||||
|
import type { CreateCalendarTypeViewModel } from "@/viewmodels/admin/calendarType.models";
|
||||||
|
import { Listbox, ListboxButton, ListboxOptions, ListboxOption, ListboxLabel } from "@headlessui/vue";
|
||||||
|
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/vue/20/solid";
|
||||||
|
import type { CalendarFieldType } from "@/types/fieldTypes";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default defineComponent({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
status: null as null | "loading" | { status: "success" | "failed"; reason?: string },
|
||||||
|
timeout: undefined as any,
|
||||||
|
selectedFields: [] as Array<CalendarFieldType>,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
beforeUnmount() {
|
||||||
|
try {
|
||||||
|
clearTimeout(this.timeout);
|
||||||
|
} catch (error) {}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions(useModalStore, ["closeModal"]),
|
||||||
|
...mapActions(useCalendarTypeStore, ["createCalendarType"]),
|
||||||
|
triggerCreate(e: any) {
|
||||||
|
let formData = e.target.elements;
|
||||||
|
let createCalendarType: CreateCalendarTypeViewModel = {
|
||||||
|
type: formData.type.value,
|
||||||
|
color: formData.color.value,
|
||||||
|
nscdr: formData.nscdr.checked,
|
||||||
|
};
|
||||||
|
this.createCalendarType(createCalendarType)
|
||||||
|
.then(() => {
|
||||||
|
this.status = { status: "success" };
|
||||||
|
this.timeout = setTimeout(() => {
|
||||||
|
this.closeModal();
|
||||||
|
}, 1500);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.status = { status: "failed" };
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -0,0 +1,72 @@
|
||||||
|
<template>
|
||||||
|
<div class="w-full md:max-w-md">
|
||||||
|
<div class="flex flex-col items-center">
|
||||||
|
<p class="text-xl font-medium">Termintyp {{ communicationType?.type }} 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 != null">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 { useCommunicationTypeStore } from "@/stores/admin/communicationType";
|
||||||
|
</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(useCommunicationTypeStore, ["communicationTypes"]),
|
||||||
|
communicationType() {
|
||||||
|
return this.communicationTypes.find((r) => r.id == this.data);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions(useModalStore, ["closeModal"]),
|
||||||
|
...mapActions(useCommunicationTypeStore, ["deleteCommunicationType"]),
|
||||||
|
triggerDelete() {
|
||||||
|
this.deleteCommunicationType(this.data)
|
||||||
|
.then(() => {
|
||||||
|
this.status = { status: "success" };
|
||||||
|
this.timeout = setTimeout(() => {
|
||||||
|
this.closeModal();
|
||||||
|
}, 1500);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.status = { status: "failed" };
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -213,13 +213,6 @@ const router = createRouter({
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: "executive-position",
|
|
||||||
name: "admin-settings-executive_position",
|
|
||||||
component: () => import("@/views/admin/settings/ExecutivePosition.vue"),
|
|
||||||
meta: { type: "read", section: "settings", module: "executive_position" },
|
|
||||||
beforeEnter: [abilityAndNavUpdate],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "executive-position",
|
path: "executive-position",
|
||||||
name: "admin-settings-executive_position-route",
|
name: "admin-settings-executive_position-route",
|
||||||
|
@ -231,6 +224,8 @@ const router = createRouter({
|
||||||
path: "",
|
path: "",
|
||||||
name: "admin-settings-executive_position",
|
name: "admin-settings-executive_position",
|
||||||
component: () => import("@/views/admin/settings/ExecutivePosition.vue"),
|
component: () => import("@/views/admin/settings/ExecutivePosition.vue"),
|
||||||
|
meta: { type: "read", section: "settings", module: "executive_position" },
|
||||||
|
beforeEnter: [abilityAndNavUpdate],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: ":id/edit",
|
path: ":id/edit",
|
||||||
|
@ -286,6 +281,28 @@ const router = createRouter({
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "calendar-type",
|
||||||
|
name: "admin-settings-calendar_type-route",
|
||||||
|
component: () => import("@/views/RouterView.vue"),
|
||||||
|
meta: { type: "read", section: "settings", module: "calendar_type" },
|
||||||
|
beforeEnter: [abilityAndNavUpdate],
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "",
|
||||||
|
name: "admin-settings-calendar_type",
|
||||||
|
component: () => import("@/views/admin/settings/CalendarType.vue"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: ":id/edit",
|
||||||
|
name: "admin-settings-calendar_type-edit",
|
||||||
|
component: () => import("@/views/admin/settings/CalendarTypeEdit.vue"),
|
||||||
|
meta: { type: "update", section: "settings", module: "calendar_type" },
|
||||||
|
beforeEnter: [abilityAndNavUpdate],
|
||||||
|
props: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
58
src/stores/admin/calendarType.ts
Normal file
58
src/stores/admin/calendarType.ts
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
import { defineStore } from "pinia";
|
||||||
|
import type {
|
||||||
|
CreateCalendarTypeViewModel,
|
||||||
|
UpdateCalendarTypeViewModel,
|
||||||
|
CalendarTypeViewModel,
|
||||||
|
} from "@/viewmodels/admin/calendarType.models";
|
||||||
|
import { http } from "@/serverCom";
|
||||||
|
import type { AxiosResponse } from "axios";
|
||||||
|
|
||||||
|
export const useCalendarTypeStore = defineStore("calendarType", {
|
||||||
|
state: () => {
|
||||||
|
return {
|
||||||
|
calendarTypes: [] as Array<CalendarTypeViewModel>,
|
||||||
|
loading: "loading" as "loading" | "fetched" | "failed",
|
||||||
|
loadingFields: "loading" as "loading" | "fetched" | "failed",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
fetchCalendarTypes() {
|
||||||
|
this.loading = "loading";
|
||||||
|
http
|
||||||
|
.get("/admin/calendar/types")
|
||||||
|
.then((result) => {
|
||||||
|
this.calendarTypes = result.data;
|
||||||
|
this.loading = "fetched";
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
this.loading = "failed";
|
||||||
|
});
|
||||||
|
},
|
||||||
|
fetchCalendarTypeById(id: number): Promise<AxiosResponse<any, any>> {
|
||||||
|
return http.get(`/admin/calendar/type/${id}`);
|
||||||
|
},
|
||||||
|
async createCalendarType(calendarType: CreateCalendarTypeViewModel): Promise<AxiosResponse<any, any>> {
|
||||||
|
const result = await http.post(`/admin/calendar/type`, {
|
||||||
|
type: calendarType.type,
|
||||||
|
nscdr: calendarType.nscdr,
|
||||||
|
color: calendarType.color,
|
||||||
|
});
|
||||||
|
this.fetchCalendarTypes();
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
async updateActiveCalendarType(calendarType: UpdateCalendarTypeViewModel): Promise<AxiosResponse<any, any>> {
|
||||||
|
const result = await http.patch(`/admin/calendar/type/${calendarType.id}`, {
|
||||||
|
type: calendarType.type,
|
||||||
|
nscdr: calendarType.nscdr,
|
||||||
|
color: calendarType.color,
|
||||||
|
});
|
||||||
|
this.fetchCalendarTypes();
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
async deleteCalendarType(calendarType: number): Promise<AxiosResponse<any, any>> {
|
||||||
|
const result = await http.delete(`/admin/calendar/type/${calendarType}`);
|
||||||
|
this.fetchCalendarTypes();
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
|
@ -87,9 +87,9 @@ export const useNavigationStore = defineStore("navigation", {
|
||||||
mainTitle: "Verein",
|
mainTitle: "Verein",
|
||||||
main: [
|
main: [
|
||||||
...(abilityStore.can("read", "club", "member") ? [{ key: "member", title: "Mitglieder" }] : []),
|
...(abilityStore.can("read", "club", "member") ? [{ key: "member", title: "Mitglieder" }] : []),
|
||||||
...(abilityStore.can("read", "club", "calendar") ? [{ key: "calendar", title: "Termine" }] : []),
|
...(abilityStore.can("read", "club", "calendar") ? [{ key: "calendar", title: "Kalender" }] : []),
|
||||||
...(abilityStore.can("read", "club", "newsletter") ? [{ key: "newsletter", title: "Newsletter" }] : []),
|
|
||||||
...(abilityStore.can("read", "club", "protocoll") ? [{ key: "protocol", title: "Protokolle" }] : []),
|
...(abilityStore.can("read", "club", "protocoll") ? [{ key: "protocol", title: "Protokolle" }] : []),
|
||||||
|
...(abilityStore.can("read", "club", "newsletter") ? [{ key: "newsletter", title: "Newsletter" }] : []),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
|
@ -108,6 +108,9 @@ export const useNavigationStore = defineStore("navigation", {
|
||||||
...(abilityStore.can("read", "settings", "membership_status")
|
...(abilityStore.can("read", "settings", "membership_status")
|
||||||
? [{ key: "membership_status", title: "Mitgliedsstatus" }]
|
? [{ key: "membership_status", title: "Mitgliedsstatus" }]
|
||||||
: []),
|
: []),
|
||||||
|
...(abilityStore.can("read", "settings", "calendar_type")
|
||||||
|
? [{ key: "calendar_type", title: "Terminarten" }]
|
||||||
|
: []),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
user: {
|
user: {
|
||||||
|
|
|
@ -10,6 +10,7 @@ export type PermissionModule =
|
||||||
| "executive_position"
|
| "executive_position"
|
||||||
| "communication"
|
| "communication"
|
||||||
| "membership_status"
|
| "membership_status"
|
||||||
|
| "calendar_type"
|
||||||
| "user"
|
| "user"
|
||||||
| "role";
|
| "role";
|
||||||
|
|
||||||
|
@ -45,12 +46,13 @@ export const permissionModules: Array<PermissionModule> = [
|
||||||
"executive_position",
|
"executive_position",
|
||||||
"communication",
|
"communication",
|
||||||
"membership_status",
|
"membership_status",
|
||||||
|
"calendar_type",
|
||||||
"user",
|
"user",
|
||||||
"role",
|
"role",
|
||||||
];
|
];
|
||||||
export const permissionTypes: Array<PermissionType> = ["read", "create", "update", "delete"];
|
export const permissionTypes: Array<PermissionType> = ["read", "create", "update", "delete"];
|
||||||
export const sectionsAndModules: SectionsAndModulesObject = {
|
export const sectionsAndModules: SectionsAndModulesObject = {
|
||||||
club: ["member", "calendar", "newsletter", "protocoll"],
|
club: ["member", "calendar", "newsletter", "protocoll"],
|
||||||
settings: ["qualification", "award", "executive_position", "communication", "membership_status"],
|
settings: ["qualification", "award", "executive_position", "communication", "membership_status", "calendar_type"],
|
||||||
user: ["user", "role"],
|
user: ["user", "role"],
|
||||||
};
|
};
|
||||||
|
|
11
src/viewmodels/admin/calendar.models.ts
Normal file
11
src/viewmodels/admin/calendar.models.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import type { CalendarTypeViewModel } from "./calendarType.models";
|
||||||
|
|
||||||
|
export interface CalendarViewModel {
|
||||||
|
id: number;
|
||||||
|
date: Date;
|
||||||
|
starttime: Date;
|
||||||
|
endtime: Date;
|
||||||
|
title: string;
|
||||||
|
content: string;
|
||||||
|
type: CalendarTypeViewModel;
|
||||||
|
}
|
19
src/viewmodels/admin/calendarType.models.ts
Normal file
19
src/viewmodels/admin/calendarType.models.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
export interface CalendarTypeViewModel {
|
||||||
|
id: number;
|
||||||
|
type: string;
|
||||||
|
nscdr: boolean;
|
||||||
|
color: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateCalendarTypeViewModel {
|
||||||
|
type: string;
|
||||||
|
nscdr: boolean;
|
||||||
|
color: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateCalendarTypeViewModel {
|
||||||
|
id: number;
|
||||||
|
type: string;
|
||||||
|
nscdr: boolean;
|
||||||
|
color: string;
|
||||||
|
}
|
54
src/views/admin/settings/CalendarType.vue
Normal file
54
src/views/admin/settings/CalendarType.vue
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
<template>
|
||||||
|
<MainTemplate>
|
||||||
|
<template #topBar>
|
||||||
|
<div class="flex flex-row items-center justify-between pt-5 pb-3 px-7">
|
||||||
|
<h1 class="font-bold text-xl h-8">Termintyp</h1>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #diffMain>
|
||||||
|
<div class="flex flex-col gap-4 grow pl-7">
|
||||||
|
<div class="flex flex-col gap-2 grow overflow-y-scroll pr-7">
|
||||||
|
<CalendarTypeListItem
|
||||||
|
v-for="calendarType in calendarTypes"
|
||||||
|
:key="calendarType.id"
|
||||||
|
:calendarType="calendarType"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-row gap-4">
|
||||||
|
<button primary class="!w-fit" @click="openCreateModal">Termintyp 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 { useCalendarTypeStore } from "@/stores/admin/calendarType";
|
||||||
|
import CalendarTypeListItem from "@/components/admin/settings/calendarType/CalendarTypeListItem.vue";
|
||||||
|
import { useModalStore } from "@/stores/modal";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default defineComponent({
|
||||||
|
computed: {
|
||||||
|
...mapState(useCalendarTypeStore, ["calendarTypes"]),
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.fetchCalendarTypes();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions(useCalendarTypeStore, ["fetchCalendarTypes"]),
|
||||||
|
...mapActions(useModalStore, ["openModal"]),
|
||||||
|
openCreateModal() {
|
||||||
|
this.openModal(
|
||||||
|
markRaw(
|
||||||
|
defineAsyncComponent(() => import("@/components/admin/settings/calendarType/CreateCalendarTypeModal.vue"))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
132
src/views/admin/settings/CalendarTypeEdit.vue
Normal file
132
src/views/admin/settings/CalendarTypeEdit.vue
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
<template>
|
||||||
|
<MainTemplate>
|
||||||
|
<template #headerInsert>
|
||||||
|
<RouterLink to="../" class="text-primary">zurück zur Liste (abbrechen)</RouterLink>
|
||||||
|
</template>
|
||||||
|
<template #topBar>
|
||||||
|
<div class="flex flex-row items-center justify-between pt-5 pb-3 px-7">
|
||||||
|
<h1 class="font-bold text-xl h-8">Termintyp {{ origin?.type }} - Daten bearbeiten</h1>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #main>
|
||||||
|
<Spinner v-if="loading == 'loading'" class="mx-auto" />
|
||||||
|
<p v-else-if="loading == 'failed'">laden fehlgeschlagen</p>
|
||||||
|
<form
|
||||||
|
v-else-if="calendarType != null"
|
||||||
|
class="flex flex-col gap-4 py-2 w-full max-w-xl mx-auto"
|
||||||
|
@submit.prevent="triggerUpdate"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<label for="type">Bezeichnung</label>
|
||||||
|
<input type="text" id="type" required v-model="calendarType.type" />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-row items-center gap-2">
|
||||||
|
<input type="color" id="color" required v-model="calendarType.color" class="!px-1 !py-0 !w-10" />
|
||||||
|
<label for="color">Farbe</label>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-row items-center gap-2">
|
||||||
|
<input type="checkbox" id="nscdr" v-model="calendarType.nscdr" />
|
||||||
|
<label for="nscdr">Standard Kalender Auslieferung (optional)</label>
|
||||||
|
</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 { useCalendarTypeStore } from "@/stores/admin/calendarType";
|
||||||
|
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 { CalendarTypeViewModel, UpdateCalendarTypeViewModel } from "@/viewmodels/admin/calendarType.models";
|
||||||
|
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/vue/20/solid";
|
||||||
|
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 | CalendarTypeViewModel,
|
||||||
|
calendarType: null as null | CalendarTypeViewModel,
|
||||||
|
timeout: null as any,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
canSaveOrReset(): boolean {
|
||||||
|
return isEqual(this.origin, this.calendarType);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.fetchItem();
|
||||||
|
},
|
||||||
|
beforeUnmount() {
|
||||||
|
try {
|
||||||
|
clearTimeout(this.timeout);
|
||||||
|
} catch (error) {}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions(useCalendarTypeStore, ["fetchCalendarTypeById", "updateActiveCalendarType"]),
|
||||||
|
resetForm() {
|
||||||
|
this.calendarType = cloneDeep(this.origin);
|
||||||
|
},
|
||||||
|
fetchItem() {
|
||||||
|
this.fetchCalendarTypeById(parseInt(this.id ?? ""))
|
||||||
|
.then((result) => {
|
||||||
|
this.calendarType = result.data;
|
||||||
|
this.origin = cloneDeep(result.data);
|
||||||
|
this.loading = "fetched";
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
this.loading = "failed";
|
||||||
|
});
|
||||||
|
},
|
||||||
|
triggerUpdate(e: any) {
|
||||||
|
if (this.calendarType == null) return;
|
||||||
|
let formData = e.target.elements;
|
||||||
|
let updateCalendarType: UpdateCalendarTypeViewModel = {
|
||||||
|
id: this.calendarType.id,
|
||||||
|
type: formData.type.value,
|
||||||
|
color: formData.color.value,
|
||||||
|
nscdr: formData.nscdr.checked,
|
||||||
|
};
|
||||||
|
this.status = "loading";
|
||||||
|
this.updateActiveCalendarType(updateCalendarType)
|
||||||
|
.then(() => {
|
||||||
|
this.fetchItem();
|
||||||
|
this.status = { status: "success" };
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
this.status = { status: "failed" };
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.timeout = setTimeout(() => {
|
||||||
|
this.status = null;
|
||||||
|
}, 2000);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
Loading…
Reference in a new issue