diff options
| author | Rasmus Fuhse <fuhse@data-quest.de> | 2025-12-19 12:20:47 +0000 |
|---|---|---|
| committer | Rasmus Fuhse <fuhse@data-quest.de> | 2025-12-19 12:20:47 +0000 |
| commit | 680de640261a0b9005a6b3c084506d6abf51b433 (patch) | |
| tree | 3527474ecb01e621b4bbbf338f29ce6888ae1380 /resources/vue | |
| parent | 47fd6fe31f93c06f816d4bb27e8fdb6c013af606 (diff) | |
Resolve "Widget Aktiver Prozesse"
Closes #5675
Merge request studip/studip!4299
Diffstat (limited to 'resources/vue')
| -rw-r--r-- | resources/vue/apps/RunningProcesses.vue | 131 | ||||
| -rw-r--r-- | resources/vue/utils/getRemainingTime.js | 65 |
2 files changed, 196 insertions, 0 deletions
diff --git a/resources/vue/apps/RunningProcesses.vue b/resources/vue/apps/RunningProcesses.vue new file mode 100644 index 0000000..852aacd --- /dev/null +++ b/resources/vue/apps/RunningProcesses.vue @@ -0,0 +1,131 @@ +<template> + <div class="running_processes"> + <article class="studip"> + <section> + <template v-if="sortedContexts.length === 0"> + {{ $gettext('Es sind derzeit keine laufenden Prozesse vorhanden. Sobald für Sie relevante Aufgaben – zum Beispiel Fragebögen aus Ihren Veranstaltungen oder Einrichtungen – verfügbar sind, erscheinen diese hier.') }} + </template> + <ul class="clean" v-if="sortedContexts.length > 0"> + <li v-for="context in sortedContexts" :key="context.id"> + <a class="context" :href="context.url"> + <span class="my-courses-avatar course-avatar-small" + :style="'background-image: url(' + context.avatar + ')'"></span> + {{ context.name }} + </a> + <ul class="processes"> + <li v-for="process in getProcessesForContext(context)" :key="process.id" class="running_process"> + + <a :href="process.url" + :data-dialog="process.dialog ? 'size=auto' : null"> + <img :src="process.icon" aria-hidden="true"> + </a> + <div class="process_right_side"> + <div class="process_text_info"> + <div> + <a :href="process.url" + :data-dialog="process.dialog ? 'size=auto' : null"> + {{ process.type }} + {{ process.title }} + </a> + <span v-if="process.additionalShortInfo" + :title="process.additionalInfoTitleTag" + class="additionalShortInfo"> + {{ process.additionalShortInfo }} + </span> + </div> + <div :title="getDatetimeInfo(process)" aria-live="off"> + {{ getRemainingTime(process) }} + </div> + </div> + <div class="progressbar_container"> + <div class="progress_bar"> + <div :class="process.end - (currentTime / 1000) <= 86400 ? 'progress alerted' : 'progress'" + :style="'width: ' + getProcessPercentage(process) + '%;'"></div> + </div> + </div> + </div> + + </li> + </ul> + </li> + </ul> + </section> + </article> + </div> +</template> + +<script setup> +import { ref, computed, onMounted, onBeforeUnmount } from 'vue'; +import { $ngettext } from "../../assets/javascripts/lib/gettext"; +import { getRemainingTime as calculateRemainingTime } from "../utils/getRemainingTime"; + +const props = defineProps({ + contexts: { + type: Object, + required: true + }, + processes: { + type: Array, + required: true + } +}); + +const currentTime = ref(Date.now()); +const intervalId = ref(null); + +const sortedContexts = computed(() => { + const contexts = Object.values(props.contexts); + return contexts.sort((a, b) => { + const a_short_end = getProcessesForContext(a)[0].end; + const b_short_end = getProcessesForContext(b)[0].end; + return a_short_end > b_short_end ? 1 : -1; + }); +}); + +function getProcessesForContext(context) { + const processes = props.processes.filter(process => process.context_id === context.id); + return processes.sort((a, b) => { + return a.end > b.end ? 1 : -1; + }); +} + +function getProcessPercentage(process) { + const now = currentTime.value / 1000; + if (now > process.end) { + return 100; + } + if (now < process.begin) { + return 0; + } + + return Math.round((now - process.begin) / (process.end - process.begin) * 100); +} + +function getRemainingTime(process) { + const now = Math.floor(currentTime.value / 1000); + + if (now > process.end) { + return this.$gettext('Beendet'); + } + if (now < process.begin) { + return this.$gettext('Noch nicht gestartet'); + } + return calculateRemainingTime(process.end - now, $ngettext); +} + +function getDatetimeInfo(process) { + return STUDIP.DateTime.getStudipDate(new Date(process.end * 1000)); +} + +onMounted(() => { + intervalId.value = window.setInterval(() => { + currentTime.value = Date.now(); + }, 1000); +}); + +onBeforeUnmount(() => { + if (intervalId.value) { + clearInterval(intervalId.value); + } +}); +</script> diff --git a/resources/vue/utils/getRemainingTime.js b/resources/vue/utils/getRemainingTime.js new file mode 100644 index 0000000..2d66606 --- /dev/null +++ b/resources/vue/utils/getRemainingTime.js @@ -0,0 +1,65 @@ +/** + * Berechnet die verbleibende Zeit eines Prozesses als formatierten String + * @param {Object} process - Der Prozess mit begin und end Timestamp (in Sekunden) + * @param {number} currentTime - Die aktuelle Zeit in Millisekunden (Date.now()) + * @param {Function} gettext - Die Gettext-Funktion für Übersetzungen + * @returns {string} - Formatierte verbleibende Zeit + */ +export function getRemainingTime(remainingSeconds, ngettext) { + if (remainingSeconds < 61) { + return ngettext( + '%{seconds} Sekunde', + '%{seconds} Sekunden', + remainingSeconds, + { minutes: remainingSeconds } + ); + } + if (remainingSeconds < 3601) { + //return gettext('%{minutes} Minuten', {minutes: Math.round(remainingSeconds / 60)}); + return ngettext( + '%{minutes} Sekunde', + '%{minutes} Sekunden', + Math.round(remainingSeconds / 60), + { minutes: Math.round(remainingSeconds / 60) } + ); + } + if (remainingSeconds < 86401) { + return ngettext( + '%{hours} Stunde', + '%{hours} Stunden', + Math.round(remainingSeconds / 3600), + { hours: Math.round(remainingSeconds / 3600) } + ); + } + if (remainingSeconds < 604801) { + return ngettext( + '%{days} Tag', + '%{days} Tage', + Math.round(remainingSeconds / 86400), + { days: Math.round(remainingSeconds / 86400) } + ); + } + if (remainingSeconds < 31536001) { + return ngettext( + '%{weeks} Woche', + '%{weeks} Wochen', + Math.round(remainingSeconds / 604800), + { weeks: Math.round(remainingSeconds / 604800) } + ); + } + if (remainingSeconds < 315360001) { + return ngettext( + '%{months} Monat', + '%{months} Monate', + Math.round(remainingSeconds / 2628000), + { months: Math.round(remainingSeconds / 2628000) } + ); + } + + return ngettext( + '%{years} Jahr', + '%{years} Jahre', + Math.round(remainingSeconds / 31536000), + { years: Math.round(remainingSeconds / 31536000) } + ); +} |
