basic routing and info display

This commit is contained in:
Julian Krauser 2024-10-04 12:47:04 +02:00
parent c1e9784b4a
commit e70e6644a6
12 changed files with 211 additions and 125 deletions

View file

@ -0,0 +1,8 @@
export const toolbarOptions = [
[{ header: [1, 2, false] }, { font: [] }],
[{ header: 1 }, { header: 2 }],
["bold", "italic", "underline", "strike"],
["blockquote", "code-block", "link"],
[{ list: "ordered" }, { list: "bullet" }, { list: "check" }],
["clean"],
];

View file

@ -68,7 +68,7 @@ export async function isAuthenticatedPromise(): Promise<Payload> {
var { firstname, lastname, mail, username, permissions } = decoded;
if (Object.keys(permissions).length === 0) {
if (Object.keys(permissions ?? {}).length === 0) {
auth.setFailed();
reject("nopermissions");
}

View file

@ -174,11 +174,27 @@ const router = createRouter({
props: true,
},
{
path: "edit",
name: "admin-club-protocol-edit",
component: () => import("@/views/admin/protocol/ProtocolEdit.vue"),
meta: { type: "update", section: "club", module: "member" },
beforeEnter: [abilityAndNavUpdate],
path: "precense",
name: "admin-club-protocol-precense",
component: () => import("@/views/admin/protocol/ProtocolPrecense.vue"),
props: true,
},
{
path: "voting",
name: "admin-club-protocol-voting",
component: () => import("@/views/admin/protocol/ProtocolVoting.vue"),
props: true,
},
{
path: "decisions",
name: "admin-club-protocol-decisions",
component: () => import("@/views/admin/protocol/ProtocolDecisions.vue"),
props: true,
},
{
path: "protocol",
name: "admin-club-protocol-protocol",
component: () => import("@/views/admin/protocol/ProtocolProtocol.vue"),
props: true,
},
],

View file

@ -13,6 +13,7 @@ export const useProtocolStore = defineStore("protocol", {
activeProtocol: null as number | null,
activeProtocolObj: null as ProtocolViewModel | null,
loadingActive: "loading" as "loading" | "fetched" | "failed",
syncing: "synced" as "synced" | "syncing" | "detectedChanges" | "failed",
};
},
actions: {

View file

@ -2,15 +2,24 @@ export interface ProtocolViewModel {
id: number;
title: string;
date: Date;
starttime: Date;
endtime: Date;
summary: string;
}
export interface CreateProtocolViewModel {
title: string;
date: Date;
starttime: Date;
endtime: Date;
summary: string;
}
export interface UpdateProtocolViewModel {
id: number;
title: string;
date: Date;
starttime: Date;
endtime: Date;
summary: string;
}

View file

@ -0,0 +1,29 @@
<template>
<div class="flex flex-col gap-2 h-full w-full overflow-y-auto">
<Spinner v-if="loadingActive == 'loading'" class="mx-auto" />
<p v-else-if="loadingActive == 'failed'" @click="" class="cursor-pointer">&#8634; laden fehlgeschlagen</p>
</div>
</template>
<script setup lang="ts">
import { defineComponent } from "vue";
import { mapActions, mapState } from "pinia";
import Spinner from "@/components/Spinner.vue";
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";
</script>
<script lang="ts">
export default defineComponent({
props: {
protocolId: String,
},
computed: {
...mapState(useProtocolStore, ["loadingActive"]),
},
mounted() {},
methods: {},
});
</script>

View file

@ -1,111 +0,0 @@
<template>
<div class="flex flex-col gap-2 h-full w-full overflow-y-auto">
<Spinner v-if="loading == 'loading'" class="mx-auto" />
<p v-else-if="loading == 'failed'">laden fehlgeschlagen</p>
<form
v-else-if="protocol != null"
class="flex flex-col gap-4 py-2 w-full max-w-xl mx-auto"
@submit.prevent="triggerUpdate"
>
<p class="mx-auto">Protokoll bearbeiten</p>
<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>
</div>
</template>
<script setup lang="ts">
import { defineComponent } from "vue";
import { mapActions, mapState } from "pinia";
import MainTemplate from "@/templates/Main.vue";
import { useProtocolStore } from "@/stores/admin/protocol";
import type { ProtocolViewModel, UpdateProtocolViewModel } from "@/viewmodels/admin/protocol.models";
import Spinner from "@/components/Spinner.vue";
import SuccessCheckmark from "@/components/SuccessCheckmark.vue";
import FailureXMark from "@/components/FailureXMark.vue";
import cloneDeep from "lodash.clonedeep";
import isEqual from "lodash.isEqual";
import { Salutation } from "@/enums/salutation";
</script>
<script lang="ts">
export default defineComponent({
props: {
protocolId: String,
},
watch: {
loadingActive() {
if (this.loading == "loading") {
this.loading = this.loadingActive;
}
if (this.loadingActive == "fetched") this.protocol = cloneDeep(this.activeProtocolObj);
},
},
data() {
return {
loading: "loading" as "loading" | "fetched" | "failed",
status: null as null | "loading" | { status: "success" | "failed"; reason?: string },
protocol: null as null | ProtocolViewModel,
timeout: null as any,
salutations: [] as Array<string>,
};
},
computed: {
canSaveOrReset(): boolean {
return isEqual(this.activeProtocolObj, this.protocol);
},
...mapState(useProtocolStore, ["activeProtocolObj", "loadingActive"]),
},
mounted() {
this.fetchItem();
this.salutations = Object.values(Salutation);
},
beforeUnmount() {
try {
clearTimeout(this.timeout);
} catch (error) {}
},
methods: {
...mapActions(useProtocolStore, ["fetchProtocolByActiveId", "updateActiveProtocol"]),
resetForm() {
this.protocol = cloneDeep(this.activeProtocolObj);
},
fetchItem() {
this.fetchProtocolByActiveId();
},
triggerUpdate(e: any) {
if (this.protocol == null) return;
let formData = e.target.elements;
let updateProtocol: UpdateProtocolViewModel = {
id: this.protocol.id,
title: formData.title.value,
date: formData.date.value,
};
this.status = "loading";
this.updateActiveProtocol(updateProtocol)
.then(() => {
this.fetchItem();
this.status = { status: "success" };
})
.catch((err) => {
this.status = { status: "failed" };
})
.finally(() => {
this.timeout = setTimeout(() => {
this.status = null;
}, 2000);
});
},
},
});
</script>

View file

@ -1,6 +1,37 @@
<template>
<div class="flex flex-col gap-2 h-full w-full overflow-y-auto">
<div v-if="activeProtocol != null" class="flex flex-col gap-2 w-full">Protokoll Details</div>
<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" />
</div>
<div class="w-full">
<label for="date">Datum</label>
<input type="date" id="date" v-model="activeProtocolObj.date" />
</div>
<div class="flex flex-row gap-2 w-full">
<div class="w-full">
<label for="starttime">Startzeit</label>
<input type="time" id="starttime" v-model="activeProtocolObj.starttime" />
</div>
<div class="w-full">
<label for="endtime">Endzeit</label>
<input type="time" id="endtime" v-model="activeProtocolObj.endtime" />
</div>
</div>
<div class="flex flex-col h-1/2">
<label for="summary">Zusammenfassung</label>
<QuillEditor
id="summary"
theme="snow"
placeholder="Zusammenfassung zur Sitzung..."
style="height: 250px; max-height: 250px; min-height: 250px"
contentType="html"
:toolbar="toolbarOptions"
v-model:content="activeProtocolObj.summary"
/>
</div>
</div>
<Spinner v-if="loadingActive == 'loading'" class="mx-auto" />
<p v-else-if="loadingActive == 'failed'" @click="fetchProtocolByActiveId" class="cursor-pointer">
@ -14,6 +45,9 @@ import { defineComponent } from "vue";
import { mapActions, mapState } from "pinia";
import Spinner from "@/components/Spinner.vue";
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";
</script>
<script lang="ts">
@ -22,7 +56,7 @@ export default defineComponent({
protocolId: String,
},
computed: {
...mapState(useProtocolStore, ["activeProtocol", "loadingActive"]),
...mapState(useProtocolStore, ["activeProtocol", "loadingActive", "activeProtocolObj"]),
},
mounted() {
this.fetchProtocolByActiveId();

View file

@ -0,0 +1,29 @@
<template>
<div class="flex flex-col gap-2 h-full w-full overflow-y-auto">
<Spinner v-if="loadingActive == 'loading'" class="mx-auto" />
<p v-else-if="loadingActive == 'failed'" @click="" class="cursor-pointer">&#8634; laden fehlgeschlagen</p>
</div>
</template>
<script setup lang="ts">
import { defineComponent } from "vue";
import { mapActions, mapState } from "pinia";
import Spinner from "@/components/Spinner.vue";
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";
</script>
<script lang="ts">
export default defineComponent({
props: {
protocolId: String,
},
computed: {
...mapState(useProtocolStore, ["loadingActive"]),
},
mounted() {},
methods: {},
});
</script>

View file

@ -0,0 +1,29 @@
<template>
<div class="flex flex-col gap-2 h-full w-full overflow-y-auto">
<Spinner v-if="loadingActive == 'loading'" class="mx-auto" />
<p v-else-if="loadingActive == 'failed'" @click="" class="cursor-pointer">&#8634; laden fehlgeschlagen</p>
</div>
</template>
<script setup lang="ts">
import { defineComponent } from "vue";
import { mapActions, mapState } from "pinia";
import Spinner from "@/components/Spinner.vue";
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";
</script>
<script lang="ts">
export default defineComponent({
props: {
protocolId: String,
},
computed: {
...mapState(useProtocolStore, ["loadingActive"]),
},
mounted() {},
methods: {},
});
</script>

View file

@ -8,9 +8,10 @@
<h1 class="font-bold text-xl h-8 min-h-fit grow">
{{ activeProtocolObj?.title }}, {{ activeProtocolObj?.date }}
</h1>
<RouterLink :to="{ name: 'admin-club-protocol-edit' }">
<PencilIcon class="w-5 h-5" />
</RouterLink>
<CloudIcon v-if="syncing == 'synced'" class="w-5 h-5" />
<CloudArrowUpIcon v-else-if="syncing == 'detectedChanges'" class="w-5 h-5" />
<ArrowPathIcon v-else-if="syncing == 'syncing'" class="w-5 h-5 animate-spin" />
<ExclamationTriangleIcon v-else class="w-5 h-5 animate-[ping_1s_ease-in-out_3] text-red-500" />
<TrashIcon class="w-5 h-5 cursor-pointer" @click="openDeleteModal" />
</div>
</template>
@ -48,7 +49,13 @@ import { mapActions, mapState } from "pinia";
import MainTemplate from "@/templates/Main.vue";
import { RouterLink, RouterView } from "vue-router";
import { useProtocolStore } from "@/stores/admin/protocol";
import { PencilIcon, TrashIcon } from "@heroicons/vue/24/outline";
import {
ArrowPathIcon,
CloudArrowUpIcon,
CloudIcon,
ExclamationTriangleIcon,
TrashIcon,
} from "@heroicons/vue/24/outline";
import { useModalStore } from "@/stores/modal";
</script>
@ -59,11 +66,17 @@ export default defineComponent({
},
data() {
return {
tabs: [{ route: "admin-club-protocol-overview", title: "Übersicht" }],
tabs: [
{ route: "admin-club-protocol-overview", title: "Übersicht" },
{ route: "admin-club-protocol-precense", title: "Anwesenheit" },
{ route: "admin-club-protocol-voting", title: "Abstimmungen" },
{ route: "admin-club-protocol-decisions", title: "Entscheidungen" },
{ route: "admin-club-protocol-protocol", title: "Protokoll" },
],
};
},
computed: {
...mapState(useProtocolStore, ["activeProtocolObj"]),
...mapState(useProtocolStore, ["activeProtocolObj", "syncing"]),
},
mounted() {
this.fetchProtocolByActiveId();

View file

@ -0,0 +1,29 @@
<template>
<div class="flex flex-col gap-2 h-full w-full overflow-y-auto">
<Spinner v-if="loadingActive == 'loading'" class="mx-auto" />
<p v-else-if="loadingActive == 'failed'" @click="" class="cursor-pointer">&#8634; laden fehlgeschlagen</p>
</div>
</template>
<script setup lang="ts">
import { defineComponent } from "vue";
import { mapActions, mapState } from "pinia";
import Spinner from "@/components/Spinner.vue";
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";
</script>
<script lang="ts">
export default defineComponent({
props: {
protocolId: String,
},
computed: {
...mapState(useProtocolStore, ["loadingActive"]),
},
mounted() {},
methods: {},
});
</script>