decisions and voting

This commit is contained in:
Julian Krauser 2024-10-18 14:20:58 +02:00
parent c0bfc00862
commit d62436722a
6 changed files with 246 additions and 34 deletions

View file

@ -20,6 +20,8 @@ import { useProtocolStore } from "@/stores/admin/protocol";
import { ArrowPathIcon, CloudArrowUpIcon, CloudIcon, ExclamationTriangleIcon } from "@heroicons/vue/24/outline"; import { ArrowPathIcon, CloudArrowUpIcon, CloudIcon, ExclamationTriangleIcon } from "@heroicons/vue/24/outline";
import { useProtocolAgendaStore } from "@/stores/admin/protocolAgenda"; import { useProtocolAgendaStore } from "@/stores/admin/protocolAgenda";
import { useProtocolPresenceStore } from "@/stores/admin/protocolPresence"; import { useProtocolPresenceStore } from "@/stores/admin/protocolPresence";
import { useProtocolDecisionStore } from "../../../../stores/admin/protocolDecision";
import { useProtocolVotingStore } from "../../../../stores/admin/protocolVoting";
</script> </script>
<script lang="ts"> <script lang="ts">
@ -44,7 +46,6 @@ export default defineComponent({
}, 10000); }, 10000);
}, },
detectedChangeProtocolAgenda() { detectedChangeProtocolAgenda() {
console.log(this.detectedChangeProtocolAgenda);
clearTimeout(this.protocolAgendaTimer); clearTimeout(this.protocolAgendaTimer);
if (this.detectedChangeProtocolAgenda == false) { if (this.detectedChangeProtocolAgenda == false) {
this.setProtocolAgendaSyncingState("synced"); this.setProtocolAgendaSyncingState("synced");
@ -66,6 +67,28 @@ export default defineComponent({
this.synchronizeActiveProtocolPresence(); this.synchronizeActiveProtocolPresence();
}, 10000); }, 10000);
}, },
detectedChangeProtocolDecision() {
clearTimeout(this.protocolDecisionTimer);
this.setProtocolDecisionSyncingState("synced");
if (this.detectedChangeProtocolDecision == false) {
return;
}
this.setProtocolDecisionSyncingState("detectedChanges");
this.protocolDecisionTimer = setTimeout(() => {
this.synchronizeActiveProtocolDecision();
}, 10000);
},
detectedChangeProtocolVoting() {
clearTimeout(this.protocolVotingTimer);
this.setProtocolVotingSyncingState("synced");
if (this.detectedChangeProtocolVoting == false) {
return;
}
this.setProtocolVotingSyncingState("detectedChanges");
this.protocolVotingTimer = setTimeout(() => {
this.synchronizeActiveProtocolVoting();
}, 10000);
},
}, },
emits: { emits: {
syncState(state: "synced" | "syncing" | "detectedChanges" | "failed") { syncState(state: "synced" | "syncing" | "detectedChanges" | "failed") {
@ -77,6 +100,8 @@ export default defineComponent({
protocolTimer: undefined as undefined | any, protocolTimer: undefined as undefined | any,
protocolAgendaTimer: undefined as undefined | any, protocolAgendaTimer: undefined as undefined | any,
protocolPresenceTimer: undefined as undefined | any, protocolPresenceTimer: undefined as undefined | any,
protocolDecisionTimer: undefined as undefined | any,
protocolVotingTimer: undefined as undefined | any,
}; };
}, },
mounted() { mounted() {
@ -86,14 +111,24 @@ export default defineComponent({
if (!this.protocolTimer) clearTimeout(this.protocolTimer); if (!this.protocolTimer) clearTimeout(this.protocolTimer);
if (!this.protocolAgendaTimer) clearTimeout(this.protocolAgendaTimer); if (!this.protocolAgendaTimer) clearTimeout(this.protocolAgendaTimer);
if (!this.protocolPresenceTimer) clearTimeout(this.protocolPresenceTimer); if (!this.protocolPresenceTimer) clearTimeout(this.protocolPresenceTimer);
if (!this.protocolDecisionTimer) clearTimeout(this.protocolDecisionTimer);
if (!this.protocolVotingTimer) clearTimeout(this.protocolVotingTimer);
}, },
computed: { computed: {
...mapState(useProtocolStore, ["syncingProtocol", "detectedChangeProtocol"]), ...mapState(useProtocolStore, ["syncingProtocol", "detectedChangeProtocol"]),
...mapState(useProtocolAgendaStore, ["syncingProtocolAgenda", "detectedChangeProtocolAgenda"]), ...mapState(useProtocolAgendaStore, ["syncingProtocolAgenda", "detectedChangeProtocolAgenda"]),
...mapState(useProtocolPresenceStore, ["syncingProtocolPresence", "detectedChangeProtocolPresence"]), ...mapState(useProtocolPresenceStore, ["syncingProtocolPresence", "detectedChangeProtocolPresence"]),
...mapState(useProtocolDecisionStore, ["syncingProtocolDecision", "detectedChangeProtocolDecision"]),
...mapState(useProtocolVotingStore, ["syncingProtocolVoting", "detectedChangeProtocolVoting"]),
syncing(): "synced" | "syncing" | "detectedChanges" | "failed" { syncing(): "synced" | "syncing" | "detectedChanges" | "failed" {
let states = [this.syncingProtocol, this.syncingProtocolAgenda, this.syncingProtocolPresence]; let states = [
this.syncingProtocol,
this.syncingProtocolAgenda,
this.syncingProtocolPresence,
this.syncingProtocolDecision,
this.syncingProtocolVoting,
];
if (states.includes("failed")) return "failed"; if (states.includes("failed")) return "failed";
else if (states.includes("syncing")) return "syncing"; else if (states.includes("syncing")) return "syncing";
@ -105,13 +140,21 @@ export default defineComponent({
...mapActions(useProtocolStore, ["synchronizeActiveProtocol", "setProtocolSyncingState"]), ...mapActions(useProtocolStore, ["synchronizeActiveProtocol", "setProtocolSyncingState"]),
...mapActions(useProtocolAgendaStore, ["synchronizeActiveProtocolAgenda", "setProtocolAgendaSyncingState"]), ...mapActions(useProtocolAgendaStore, ["synchronizeActiveProtocolAgenda", "setProtocolAgendaSyncingState"]),
...mapActions(useProtocolPresenceStore, ["synchronizeActiveProtocolPresence", "setProtocolPresenceSyncingState"]), ...mapActions(useProtocolPresenceStore, ["synchronizeActiveProtocolPresence", "setProtocolPresenceSyncingState"]),
...mapActions(useProtocolDecisionStore, ["synchronizeActiveProtocolDecision", "setProtocolDecisionSyncingState"]),
...mapActions(useProtocolVotingStore, ["synchronizeActiveProtocolVoting", "setProtocolVotingSyncingState"]),
syncAll() { syncAll() {
if (!this.protocolTimer) clearTimeout(this.protocolTimer); if (!this.protocolTimer) clearTimeout(this.protocolTimer);
if (!this.protocolAgendaTimer) clearTimeout(this.protocolAgendaTimer); if (!this.protocolAgendaTimer) clearTimeout(this.protocolAgendaTimer);
if (!this.protocolPresenceTimer) clearTimeout(this.protocolPresenceTimer); if (!this.protocolPresenceTimer) clearTimeout(this.protocolPresenceTimer);
if (!this.protocolDecisionTimer) clearTimeout(this.protocolDecisionTimer);
if (!this.protocolVotingTimer) clearTimeout(this.protocolVotingTimer);
this.synchronizeActiveProtocol(); this.synchronizeActiveProtocol();
this.synchronizeActiveProtocolAgenda(); this.synchronizeActiveProtocolAgenda();
this.synchronizeActiveProtocolPresence(); this.synchronizeActiveProtocolPresence();
this.synchronizeActiveProtocolDecision();
this.synchronizeActiveProtocolVoting();
}, },
}, },
}); });

View file

@ -52,7 +52,7 @@ export const useProtocolAgendaStore = defineStore("protocolAgenda", {
id: res.data, id: res.data,
topic: "", topic: "",
context: "", context: "",
protocolId, protocolId: Number(protocolId),
}); });
}) })
.catch((err) => {}); .catch((err) => {});
@ -60,7 +60,6 @@ export const useProtocolAgendaStore = defineStore("protocolAgenda", {
async synchronizeActiveProtocolAgenda() { async synchronizeActiveProtocolAgenda() {
this.syncingProtocolAgenda = "syncing"; this.syncingProtocolAgenda = "syncing";
const protocolId = useProtocolStore().activeProtocol; const protocolId = useProtocolStore().activeProtocol;
console.log(this.agenda, this.origin, differenceWith(this.agenda, this.origin, isEqual));
await http await http
.patch(`/admin/protocol/${protocolId}/synchronize/agenda`, { .patch(`/admin/protocol/${protocolId}/synchronize/agenda`, {

View file

@ -8,38 +8,75 @@ import type {
import { useProtocolStore } from "./protocol"; import { useProtocolStore } from "./protocol";
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"; import differenceWith from "lodash.differencewith";
export const useProtocolDecisionStore = defineStore("protocolDecision", { export const useProtocolDecisionStore = defineStore("protocolDecision", {
state: () => { state: () => {
return { return {
decision: [] as Array<ProtocolDecisionViewModel>, decision: [] as Array<ProtocolDecisionViewModel>,
origin: [] as Array<ProtocolDecisionViewModel>,
loading: "loading" as "loading" | "fetched" | "failed", loading: "loading" as "loading" | "fetched" | "failed",
syncingProtocolDecision: "synced" as "synced" | "syncing" | "detectedChanges" | "failed",
}; };
}, },
getters: {
detectedChangeProtocolDecision: (state) =>
!isEqual(state.origin, state.decision) && state.syncingProtocolDecision != "syncing",
},
actions: { actions: {
setProtocolDecisionSyncingState(state: "synced" | "syncing" | "detectedChanges" | "failed") {
this.syncingProtocolDecision = state;
},
fetchProtocolDecision() { fetchProtocolDecision() {
const protocolId = useProtocolStore().activeProtocol;
this.loading = "loading"; this.loading = "loading";
http this.fetchProtocolDecisionPromise()
.get(`/admin/protocol/${protocolId}/decisions`)
.then((result) => { .then((result) => {
this.decision = result.data; this.origin = result.data;
this.decision = cloneDeep(this.origin);
this.loading = "fetched"; this.loading = "fetched";
}) })
.catch((err) => { .catch((err) => {
this.loading = "failed"; this.loading = "failed";
}); });
}, },
async synchronizeActiveProtocolDecision( fetchProtocolDecisionPromise() {
decision: Array<SyncProtocolDecisionViewModel>
): Promise<AxiosResponse<any, any>> {
const protocolId = useProtocolStore().activeProtocol; const protocolId = useProtocolStore().activeProtocol;
const result = await http.patch(`/admin/protocol/${protocolId}/synchronize/decisions`, { return http.get(`/admin/protocol/${protocolId}/decisions`);
decision: decision, },
}); createProtocolDecision() {
this.fetchProtocolDecision(); const protocolId = useProtocolStore().activeProtocol;
return result; if (protocolId == null) return;
return http
.post(`/admin/protocol/${protocolId}/decision`)
.then((res) => {
this.decision.push({
id: res.data,
topic: "",
context: "",
protocolId: Number(protocolId),
});
})
.catch((err) => {});
},
async synchronizeActiveProtocolDecision() {
this.syncingProtocolDecision = "syncing";
const protocolId = useProtocolStore().activeProtocol;
await http
.patch(`/admin/protocol/${protocolId}/synchronize/decisions`, {
decisions: differenceWith(this.decision, this.origin, isEqual),
})
.then((res) => {
this.syncingProtocolDecision = "synced";
})
.catch((err) => {
this.syncingProtocolDecision = "failed";
});
this.fetchProtocolDecisionPromise()
.then((res) => {
this.origin = res.data;
})
.catch((err) => {});
}, },
}, },
}); });

View file

@ -8,38 +8,78 @@ import type {
import { useProtocolStore } from "./protocol"; import { useProtocolStore } from "./protocol";
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"; import differenceWith from "lodash.differencewith";
export const useProtocolVotingStore = defineStore("protocolVoting", { export const useProtocolVotingStore = defineStore("protocolVoting", {
state: () => { state: () => {
return { return {
voting: [] as Array<ProtocolVotingViewModel>, voting: [] as Array<ProtocolVotingViewModel>,
origin: [] as Array<ProtocolVotingViewModel>,
loading: "loading" as "loading" | "fetched" | "failed", loading: "loading" as "loading" | "fetched" | "failed",
syncingProtocolVoting: "synced" as "synced" | "syncing" | "detectedChanges" | "failed",
}; };
}, },
getters: {
detectedChangeProtocolVoting: (state) =>
!isEqual(state.origin, state.voting) && state.syncingProtocolVoting != "syncing",
},
actions: { actions: {
setProtocolVotingSyncingState(state: "synced" | "syncing" | "detectedChanges" | "failed") {
this.syncingProtocolVoting = state;
},
fetchProtocolVoting() { fetchProtocolVoting() {
const protocolId = useProtocolStore().activeProtocol;
this.loading = "loading"; this.loading = "loading";
http this.fetchProtocolVotingPromise()
.get(`/admin/protocol/${protocolId}/votings`)
.then((result) => { .then((result) => {
this.voting = result.data; this.origin = result.data;
this.voting = cloneDeep(this.origin);
this.loading = "fetched"; this.loading = "fetched";
}) })
.catch((err) => { .catch((err) => {
this.loading = "failed"; this.loading = "failed";
}); });
}, },
async synchronizeActiveProtocolVoting( fetchProtocolVotingPromise() {
voting: Array<SyncProtocolVotingViewModel>
): Promise<AxiosResponse<any, any>> {
const protocolId = useProtocolStore().activeProtocol; const protocolId = useProtocolStore().activeProtocol;
const result = await http.patch(`/admin/protocol/${protocolId}/synchronize/votings`, { return http.get(`/admin/protocol/${protocolId}/votings`);
voting: voting, },
}); createProtocolVoting() {
this.fetchProtocolVoting(); const protocolId = useProtocolStore().activeProtocol;
return result; if (protocolId == null) return;
return http
.post(`/admin/protocol/${protocolId}/voting`)
.then((res) => {
this.voting.push({
id: res.data,
topic: "",
context: "",
favour: 0,
abstain: 0,
against: 0,
protocolId: Number(protocolId),
});
})
.catch((err) => {});
},
async synchronizeActiveProtocolVoting() {
this.syncingProtocolVoting = "syncing";
const protocolId = useProtocolStore().activeProtocol;
await http
.patch(`/admin/protocol/${protocolId}/synchronize/votings`, {
votings: differenceWith(this.voting, this.origin, isEqual),
})
.then((res) => {
this.syncingProtocolVoting = "synced";
})
.catch((err) => {
this.syncingProtocolVoting = "failed";
});
this.fetchProtocolVotingPromise()
.then((res) => {
this.origin = res.data;
})
.catch((err) => {});
}, },
}, },
}); });

View file

@ -4,12 +4,50 @@
<p v-else-if="loading == 'failed'" @click="fetchProtocolDecision" class="cursor-pointer"> <p v-else-if="loading == 'failed'" @click="fetchProtocolDecision" class="cursor-pointer">
&#8634; laden fehlgeschlagen &#8634; laden fehlgeschlagen
</p> </p>
<div class="flex flex-col gap-2 h-full overflow-y-auto">
<details
v-for="item in decision"
class="flex flex-col gap-2 rounded-lg w-full justify-between border border-primary overflow-hidden min-h-fit"
>
<summary class="flex flex-row gap-2 bg-primary p-2 w-full justify-between items-center cursor-pointer">
<svg
class="fill-white stroke-white opacity-75 w-4 h-4"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
>
<path d="M12.95 10.707l.707-.707L8 4.343 6.586 5.757 10.828 10l-4.242 4.243L8 15.657l4.95-4.95z" />
</svg>
<input
type="text"
name="title"
id="title"
placeholder="Einscheidung"
autocomplete="off"
v-model="item.topic"
@keyup.prevent
/>
</summary>
<QuillEditor
id="top"
theme="snow"
placeholder="Entscheidung Inhalt..."
style="height: 250px; max-height: 250px; min-height: 250px"
contentType="html"
:toolbar="toolbarOptions"
v-model:content="item.context"
/>
</details>
</div>
<button primary class="!w-fit" @click="createProtocolDecision">Eintrag hinzufügen</button>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { defineComponent } from "vue"; import { defineComponent } from "vue";
import { mapActions, mapState } from "pinia"; import { mapActions, mapState, mapWritableState } from "pinia";
import Spinner from "@/components/Spinner.vue"; import Spinner from "@/components/Spinner.vue";
import { useProtocolStore } from "@/stores/admin/protocol"; import { useProtocolStore } from "@/stores/admin/protocol";
import { QuillEditor } from "@vueup/vue-quill"; import { QuillEditor } from "@vueup/vue-quill";
@ -24,13 +62,13 @@ export default defineComponent({
protocolId: String, protocolId: String,
}, },
computed: { computed: {
...mapState(useProtocolDecisionStore, ["decision", "loading"]), ...mapWritableState(useProtocolDecisionStore, ["decision", "loading"]),
}, },
mounted() { mounted() {
this.fetchProtocolDecision(); this.fetchProtocolDecision();
}, },
methods: { methods: {
...mapActions(useProtocolDecisionStore, ["fetchProtocolDecision"]), ...mapActions(useProtocolDecisionStore, ["fetchProtocolDecision", "createProtocolDecision"]),
}, },
}); });
</script> </script>

View file

@ -4,6 +4,61 @@
<p v-else-if="loading == 'failed'" @click="fetchProtocolVoting" class="cursor-pointer"> <p v-else-if="loading == 'failed'" @click="fetchProtocolVoting" class="cursor-pointer">
&#8634; laden fehlgeschlagen &#8634; laden fehlgeschlagen
</p> </p>
<div class="flex flex-col gap-2 h-full overflow-y-auto">
<details
v-for="item in voting"
class="flex flex-col gap-2 rounded-lg w-full justify-between border border-primary overflow-hidden min-h-fit"
>
<summary class="flex flex-row gap-2 bg-primary p-2 w-full justify-between items-center cursor-pointer">
<svg
class="fill-white stroke-white opacity-75 w-4 h-4"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
>
<path d="M12.95 10.707l.707-.707L8 4.343 6.586 5.757 10.828 10l-4.242 4.243L8 15.657l4.95-4.95z" />
</svg>
<input
type="text"
name="title"
id="title"
placeholder="Einscheidung"
autocomplete="off"
v-model="item.topic"
@keyup.prevent
/>
</summary>
<QuillEditor
id="top"
theme="snow"
placeholder="Entscheidung Inhalt..."
style="height: 100px; max-height: 100px; min-height: 100px"
contentType="html"
:toolbar="toolbarOptions"
v-model:content="item.context"
/>
<div class="px-2 pb-2">
<p>Ergebnis:</p>
<div class="flex flex-row gap-2">
<div class="w-full">
<p>dafür</p>
<input type="number" v-model="item.favour" />
</div>
<div class="w-full">
<p>dagegen</p>
<input type="number" v-model="item.against" />
</div>
<div class="w-full">
<p>enthalten</p>
<input type="number" v-model="item.abstain" />
</div>
</div>
</div>
</details>
</div>
<button primary class="!w-fit" @click="createProtocolVoting">Abstimmung hinzufügen</button>
</div> </div>
</template> </template>
@ -30,7 +85,7 @@ export default defineComponent({
this.fetchProtocolVoting(); this.fetchProtocolVoting();
}, },
methods: { methods: {
...mapActions(useProtocolVotingStore, ["fetchProtocolVoting"]), ...mapActions(useProtocolVotingStore, ["fetchProtocolVoting", "createProtocolVoting"]),
}, },
}); });
</script> </script>