From 4099f6032f86b939892d7e360ea51bcd11957700 Mon Sep 17 00:00:00 2001
From: Ron Lucke <lucke@elan-ev.de>
Date: Thu, 22 Jun 2023 13:00:12 +0000
Subject: [PATCH] fix #2756

Closes #2756

Merge request studip/studip!1866
---
 .../CoursewareDashboardStudents.vue           | 125 ++++++++++++++----
 .../vue/mixins/courseware/task-helper.js      |   7 +-
 2 files changed, 108 insertions(+), 24 deletions(-)

diff --git a/resources/vue/components/courseware/CoursewareDashboardStudents.vue b/resources/vue/components/courseware/CoursewareDashboardStudents.vue
index c12e0b190a6..897388dbb4c 100644
--- a/resources/vue/components/courseware/CoursewareDashboardStudents.vue
+++ b/resources/vue/components/courseware/CoursewareDashboardStudents.vue
@@ -5,16 +5,26 @@
                 <col />
             </colgroup>
             <thead>
-                <tr>
-                    <th><translate>Status</translate></th>
-                    <th><translate>Aufgabentitel</translate></th>
-                    <th><translate>Teilnehmende/Gruppen</translate></th>
-                    <th><translate class="responsive-hidden">Seite</translate></th>
-                    <th><translate>bearbeitet</translate></th>
-                    <th><translate>Abgabefrist</translate></th>
-                    <th><translate>Abgabe</translate></th>
-                    <th class="responsive-hidden renewal"><translate>Verlängerungsanfrage</translate></th>
-                    <th class="responsive-hidden feedback"><translate>Feedback</translate></th>
+                <tr class="sortable">
+                    <th>{{ $gettext('Status') }}</th>
+                    <th :class="getSortClass('task-title')" @click="sort('task-title')">
+                        {{ $gettext('Aufgabentitel') }}
+                    </th>
+                    <th :class="getSortClass('solver-name')" @click="sort('solver-name')">
+                        {{ $gettext('Teilnehmende/Gruppen') }}
+                    </th>
+                    <th class="responsive-hidden" :class="getSortClass('page-title')" @click="sort('page-title')">
+                        {{ $gettext('Seite') }}
+                    </th>
+                    <th :class="getSortClass('progress')" @click="sort('progress')">
+                        {{ $gettext('bearbeitet') }}
+                    </th>
+                    <th :class="getSortClass('submission-date')" @click="sort('submission-date')">
+                        {{ $gettext('Abgabefrist') }}
+                    </th>
+                    <th>{{ $gettext('Abgabe') }}</th>
+                    <th class="responsive-hidden renewal">{{ $gettext('Verlängerungsanfrage') }}</th>
+                    <th class="responsive-hidden feedback">{{ $gettext('Feedback') }}</th>
                 </tr>
             </thead>
             <tbody>
@@ -73,14 +83,14 @@
                             class="button"
                             @click="solveRenewalRequest(task)"
                         >
-                            <translate>Anfrage bearbeiten</translate>
+                            {{ $gettext('Anfrage bearbeiten') }}
                         </button>
                         <span v-show="task.attributes.renewal === 'declined'">
                             <studip-icon shape="decline" role="status-red" />
-                            <translate>Anfrage abgelehnt</translate>
+                            {{ $gettext('Anfrage abgelehnt') }}
                         </span>
                         <span v-show="task.attributes.renewal === 'granted'">
-                            <translate>verlängert bis</translate>:
+                            {{ $gettext('verlängert bis') }}:
                             {{ getReadableDate(task.attributes['renewal-date']) }}
                         </span>
                         <studip-icon
@@ -102,7 +112,7 @@
                             "
                         >
                             <studip-icon shape="accept" role="status-green" />
-                            <translate>Feedback gegeben</translate>
+                            {{ $gettext('Feedback gegeben') }}
                             <studip-icon
                                 :title="$gettext('Feedback bearbeiten')"
                                 class="edit"
@@ -117,7 +127,7 @@
                             class="button"
                             @click="addFeedback(task)"
                         >
-                            <translate>Feedback geben</translate>
+                            {{ $gettext('Feedback geben') }}
                         </button>
                     </td>
                 </tr>
@@ -147,18 +157,18 @@
             <template v-slot:dialogContent>
                 <form class="default" @submit.prevent="">
                     <label>
-                        <translate>Fristverlängerung</translate>
+                        {{ $gettext('Fristverlängerung') }}
                         <select v-model="currentDialogTask.attributes.renewal">
                             <option value="declined">
-                                <translate>ablehnen</translate>
+                                {{ $gettext('ablehnen') }}
                             </option>
                             <option value="granted">
-                                <translate>gewähren</translate>
+                                {{ $gettext('gewähren') }}
                             </option>
                         </select>
                     </label>
                     <label v-if="currentDialogTask.attributes.renewal === 'granted'">
-                        <translate>neue Frist</translate>
+                        {{ $gettext('neue Frist') }}
                         <courseware-date-input v-model="currentDialogTask.attributes['renewal-date']" class="size-l" />
                     </label>
                 </form>
@@ -188,7 +198,7 @@
                 />
                 <form class="default" @submit.prevent="">
                     <label>
-                        <translate>Feedback</translate>
+                        {{ $gettext('Feedback') }}
                         <textarea v-model="currentDialogFeedback.attributes.content" />
                     </label>
                 </form>
@@ -210,7 +220,7 @@
             <template v-slot:dialogContent>
                 <form class="default" @submit.prevent="">
                     <label>
-                        <translate>Feedback</translate>
+                        {{ $gettext('Feedback') }}
                         <textarea v-model="currentDialogFeedback.attributes.content" />
                     </label>
                 </form>
@@ -264,6 +274,8 @@ export default {
                     close: this.$gettext('Schließen'),
                 },
             },
+            sortBy: 'task-title',
+            sortASC: true,
         };
     },
     computed: {
@@ -278,7 +290,7 @@ export default {
             showTasksDistributeDialog: 'showTasksDistributeDialog'
         }),
         tasks() {
-            return this.allTasks.map((task) => {
+            const tasks = this.allTasks.map((task) => {
                 const result = {
                     task,
                     taskGroup: this.relatedTaskGroups({ parent: task, relationship: 'task-group' }),
@@ -287,13 +299,16 @@ export default {
                     user: null,
                     group: null,
                     feedback: null,
+                    solverName: null
                 };
                 let solver = task.relationships.solver.data;
                 if (solver.type === 'users') {
                     result.user = this.userById({ id: solver.id });
+                    result.solverName = result.user.attributes['formatted-name'];
                 }
                 if (solver.type === 'status-groups') {
                     result.group = this.statusGroupById({ id: solver.id });
+                    result.solverName = result.group.attributes['name'];
                 }
 
                 const feedbackId = task.relationships['task-feedback'].data?.id;
@@ -303,6 +318,8 @@ export default {
 
                 return result;
             });
+
+            return this.sortTasks(tasks);
         },
         managerUrl() {
             return STUDIP.URLHelper.getURL('dispatch.php/course/courseware/manager', {cid: this.context.id});
@@ -396,7 +413,69 @@ export default {
                     include: 'solver, structural-element, task-feedback, task-group, task-group.lecturer'
                 }
             });
-        }
+        },
+        getSortClass(col) {
+            if (col === this.sortBy) {
+                return this.sortASC ? 'sortasc' : 'sortdesc';
+            }
+        },
+        sort(sortBy) {
+            if (this.sortBy === sortBy) {
+                this.sortASC = !this.sortASC;
+            } else {
+                this.sortBy = sortBy;
+            }
+        },
+        sortTasks(tasks) {
+            switch (this.sortBy) {
+                case 'task-title':
+                    tasks = tasks.sort((a, b) => {
+                        if (this.sortASC) {
+                            return a.taskGroup.attributes.title < b.taskGroup.attributes.title ? -1 : 1;
+                        } else {
+                            return a.taskGroup.attributes.title > b.taskGroup.attributes.title ? -1 : 1;
+                        }
+                    });
+                    break;
+                case 'solver-name':
+                    tasks = tasks.sort((a, b) => {
+                        if (this.sortASC) {
+                            return a.solverName < b.solverName ? -1 : 1;
+                        } else {
+                            return a.solverName > b.solverName ? -1 : 1;
+                        }
+                    });
+                    break;
+                case 'page-title':
+                    tasks = tasks.sort((a, b) => {
+                        if (this.sortASC) {
+                            return a.element.attributes.title < b.element.attributes.title ? -1 : 1;
+                        } else {
+                            return a.element.attributes.title > b.element.attributes.title ? -1 : 1;
+                        }
+                    });
+                    break;
+                case 'progress':
+                    tasks = tasks.sort((a, b) => {
+                        if (this.sortASC) {
+                            return a.task.attributes.progress < b.task.attributes.progress ? -1 : 1;
+                        } else {
+                            return a.task.attributes.progress > b.task.attributes.progress ? -1 : 1;
+                        }
+                    });
+                    break;
+                case 'submission-date':
+                    tasks = tasks.sort((a, b) => {
+                        if (this.sortASC) {
+                            return new Date(a.task.attributes['submission-date']) - new Date(b.task.attributes['submission-date']);
+                        } else {
+                            return new Date(b.task.attributes['submission-date']) - new Date(a.task.attributes['submission-date']);
+                        }
+                    });
+                    break;
+            }
+            return tasks;
+        },
     },
 };
 </script>
diff --git a/resources/vue/mixins/courseware/task-helper.js b/resources/vue/mixins/courseware/task-helper.js
index 9f9fdb37cdc..0bc694c1226 100644
--- a/resources/vue/mixins/courseware/task-helper.js
+++ b/resources/vue/mixins/courseware/task-helper.js
@@ -59,7 +59,12 @@ export default {
             return `${STUDIP.URLHelper.base_url}dispatch.php/course/courseware/courseware/${unitId}?cid=${STUDIP.URLHelper.parameters.cid}#/structural_element/${element.id}`;
         },
         getReadableDate(date) {
-            return new Date(date).toLocaleDateString();
+            let locale = navigator.language ? navigator.language : 'de-DE';
+            return new Date(date).toLocaleDateString(locale, {
+                year: "numeric",
+                month: "2-digit",
+                day: "2-digit",
+            });
         },
     },
 };
-- 
GitLab