aboutsummaryrefslogtreecommitdiff
path: root/resources/vue
diff options
context:
space:
mode:
authorRasmus Fuhse <fuhse@data-quest.de>2025-12-19 12:20:47 +0000
committerRasmus Fuhse <fuhse@data-quest.de>2025-12-19 12:20:47 +0000
commit680de640261a0b9005a6b3c084506d6abf51b433 (patch)
tree3527474ecb01e621b4bbbf338f29ce6888ae1380 /resources/vue
parent47fd6fe31f93c06f816d4bb27e8fdb6c013af606 (diff)
Resolve "Widget Aktiver Prozesse"
Closes #5675 Merge request studip/studip!4299
Diffstat (limited to 'resources/vue')
-rw-r--r--resources/vue/apps/RunningProcesses.vue131
-rw-r--r--resources/vue/utils/getRemainingTime.js65
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) }
+ );
+}