diff --git a/app/controllers/admin/courses.php b/app/controllers/admin/courses.php
index edf529b4ce4e0232104cf5dcf4833c1c0b1e709a..19a577f00975beaa56be849547050172ffbe96d9 100644
--- a/app/controllers/admin/courses.php
+++ b/app/controllers/admin/courses.php
@@ -29,43 +29,6 @@ require_once 'lib/archiv.inc.php'; //for lastActivity in getCourses() method
 class Admin_CoursesController extends AuthenticatedController
 {
 
-    /**
-     * This helper method retrieves the values of datafields when
-     * the user started a search for courses matching a specific value of
-     * one or more datafields.
-     * This method also checks if a datafield is activated by the user
-     * and will reject any value for datafields that aren't activated by the user.
-     *
-     * @return Array Associative array, consisting of datafield names
-     * (as array keys) and values for those datafields.
-     */
-    private function getDatafieldFilters()
-    {
-        //first get the active datafields of the user:
-        $userSelectedElements = $this->getActiveElements();
-        $activeDatafields = $userSelectedElements['datafields'] ?? [];
-
-        if (!$activeDatafields) {
-            return [];
-        }
-
-        //Ok, we have a list of active datafields whose value may be searched for.
-        //We must check for the request parameters (df_$DATAFIELD_ID)
-        //and return their IDs with a value.
-
-        $searchedDatafields = [];
-
-        foreach ($activeDatafields as $activeField) {
-            $requestParamValue = Request::get('df_'.$activeField);
-            if ($requestParamValue) {
-                $searchedDatafields[$activeField] = $requestParamValue;
-            }
-        }
-
-        return $searchedDatafields;
-    }
-
-
     /**
      * This method returns the appropriate widget for the given datafield.
      *
@@ -79,6 +42,8 @@ class Admin_CoursesController extends AuthenticatedController
             //The current user is allowed to see this datafield.
             //Now we must distinguish between the different types of data fields:
 
+            $datafields_filters = $GLOBALS['user']->cfg->ADMIN_COURSES_DATAFIELDS_FILTERS;
+
             $type = $datafield->type;
 
             if ($type == 'bool') {
@@ -86,14 +51,15 @@ class Admin_CoursesController extends AuthenticatedController
                 $checkboxWidget = new OptionsWidget($datafield->name);
                 $checkboxWidget->addCheckbox(
                     _('Feld gesetzt'),
-                    Request::bool('df_'.$datafield->id, false),
+                    Request::bool('df_'.$datafield->id, $datafields_filters[$datafield->id] ?? false),
                     URLHelper::getURL(
                         'dispatch.php/admin/courses/index',
                         ['df_'.$datafield->id => '1']
                     ),
                     URLHelper::getURL(
                         'dispatch.php/admin/courses/index'
-                    )
+                    ),
+                    ['onclick' => "$(this).toggleClass(['options-checked', 'options-unchecked']); STUDIP.AdminCourses.App.changeFilter({'df_".$datafield->id."': $(this).hasClass('options-checked') ? 1 : 0}); return false;"]
                 );
                 return $checkboxWidget;
             } elseif ($type == 'selectbox' || $type == 'radio' || $type == 'selectboxmultiple') {
@@ -103,21 +69,27 @@ class Admin_CoursesController extends AuthenticatedController
                 )));
 
                 if ($options) {
-                    $options = array_merge(
-                        [' ' => '(' . _('keine Auswahl') . ')'],
-                        $options
-                    );
-
                     $selectWidget = new SelectWidget(
                         $datafield->name,
                         '?',
                         'df_' . $datafield->id
                     );
-                    foreach($options as $option) {
+                    $selectWidget->addElement(
+                        new SelectElement(
+                            '',
+                            '(' . _('keine Auswahl') . ')'
+                        )
+                    );
+                    foreach ($options as $option) {
                         $selectWidget->addElement(
-                            new SelectElement($option, $option, Request::get('df_'.$datafield->id) == $option)
+                            new SelectElement(
+                                $option,
+                                $option,
+                                Request::get('df_'.$datafield->id, $datafields_filters[$datafield->id] ?? null) === $option
+                            )
                         );
                     }
+                    $selectWidget->setOnSubmitHandler("STUDIP.AdminCourses.App.changeFilter({'df_".$datafield->id."': $(this).find('select').val()}); return false;");
                     return $selectWidget;
                 }
                 return null;
@@ -127,8 +99,13 @@ class Admin_CoursesController extends AuthenticatedController
                 $textWidget->setTitle($datafield->name);
                 $textWidget->addNeedle(
                     '',
-                    'df_'.$datafield->id
+                    'df_'.$datafield->id,
+                    false,
+                    null,
+                    null,
+                    $datafields_filters[$datafield->id]
                 );
+                $textWidget->setOnSubmitHandler("STUDIP.AdminCourses.App.changeFilter({'df_".$datafield->id."': $(this).find('input').val()}); return false;");
                 return $textWidget;
             }
         }
@@ -144,7 +121,7 @@ class Admin_CoursesController extends AuthenticatedController
      * @param string courseTypeFilterConfig The selected value for the course type filter field, defaults to null.
      * @return null This method does not return any value.
      */
-    private function buildSidebar($courseTypeFilterConfig = null)
+    private function buildSidebar()
     {
         /*
             Depending on the elements the user has selected
@@ -153,6 +130,10 @@ class Admin_CoursesController extends AuthenticatedController
         */
         $visibleElements = $this->getActiveElements();
 
+        $this->sem_create_perm = in_array(Config::get()->SEM_CREATE_PERM, ['root', 'admin', 'dozent'])
+            ? Config::get()->SEM_CREATE_PERM
+            : 'dozent';
+
         $sidebar = Sidebar::get();
 
         /*
@@ -179,13 +160,13 @@ class Admin_CoursesController extends AuthenticatedController
             $this->setSemesterSelector();
         }
         if (!empty($visibleElements['stgteil'])) {
-            $this->setStgteilSelector();
+            Sidebar::Get()->addWidget($this->getStgteilSelector(), 'filter_stgteil');
         }
         if (!empty($visibleElements['courseType'])) {
-            $this->setCourseTypeWidget($courseTypeFilterConfig);
+            $this->setCourseTypeWidget();
         }
         if (!empty($visibleElements['teacher'])) {
-            $this->setTeacherWidget();
+            Sidebar::Get()->addWidget($this->getTeacherWidget(), 'filter_teacher');
         }
 
         //if there are datafields in the list, draw their input fields, too:
@@ -213,7 +194,7 @@ class Admin_CoursesController extends AuthenticatedController
 
 
         //this shall be visible in every case:
-        $this->setActionsWidget($this->selected_action);
+        $this->setActionsWidget();
 
 
         //actions: always visible, too
@@ -296,20 +277,15 @@ class Admin_CoursesController extends AuthenticatedController
             $this->semester = Semester::find($GLOBALS['user']->cfg->MY_COURSES_SELECTED_CYCLE);
         }
 
-        if (Request::submitted('search')) {
-            $GLOBALS['user']->cfg->store('ADMIN_COURSES_SEARCHTEXT', Request::get('search'));
-        }
         if (Request::get('reset-search')) {
             $GLOBALS['user']->cfg->delete('ADMIN_COURSES_SEARCHTEXT');
         }
-        if (Request::submitted('teacher_filter')) {
-            $GLOBALS['user']->cfg->store('ADMIN_COURSES_TEACHERFILTER', Request::option('teacher_filter'));
-        }
 
         PageLayout::setHelpKeyword('Basis.Veranstaltungen');
         PageLayout::setTitle(_('Verwaltung von Veranstaltungen und Einrichtungen'));
         // Add admission functions.
         PageLayout::addScript('studip-admission.js');
+        $this->max_show_courses = 500;
     }
 
     /**
@@ -317,67 +293,457 @@ class Admin_CoursesController extends AuthenticatedController
      */
     public function index_action()
     {
-        $this->sem_create_perm = in_array(Config::get()->SEM_CREATE_PERM, ['root', 'admin', 'dozent'])
-            ? Config::get()->SEM_CREATE_PERM
-            : 'dozent';
+        $this->fields = $this->getViewFilters();
+        $this->sortby = $GLOBALS['user']->cfg->MEINE_SEMINARE_SORT ?? 'name';
+        $this->sortflag = $GLOBALS['user']->cfg->MEINE_SEMINARE_SORT_FLAG ?? 'ASC';
+
+        $this->buildSidebar();
+
+        PageLayout::addHeadElement('script', [
+            'type' => 'text/javascript',
+        ], sprintf(
+              'window.AdminCoursesStoreData = %s;',
+              json_encode($this->getStoreData())
+        ));
+    }
+
+    private function getStoreData(): array
+    {
+        $configuration = User::findCurrent()->getConfiguration();
+
+        $institut_id = $configuration->MY_INSTITUTES_DEFAULT && $configuration->MY_INSTITUTES_DEFAULT !== 'all'
+                     ? $configuration->MY_INSTITUTES_DEFAULT
+                     : null;
+
+
+        return [
+            'setActivatedFields' => $this->getFilterConfig(),
+            'setActionArea' => $configuration->MY_COURSES_ACTION_AREA ?? '1',
+            'setFilter' => array_filter(array_merge(
+                $this->getDatafieldFilters(),
+                [
+                    'institut_id'    => $institut_id,
+                    'search'         => $configuration->ADMIN_COURSES_SEARCHTEXT,
+                    'semester_id'    => $configuration->MY_COURSES_SELECTED_CYCLE,
+                    'course_type'    => $configuration->MY_COURSES_TYPE_FILTER,
+                    'stgteil'        => $configuration->MY_COURSES_SELECTED_STGTEIL,
+                    'teacher_filter' => $configuration->ADMIN_COURSES_TEACHERFILTER,
+                ]
+            )),
+        ];
+    }
+
+    private function getDatafieldFilters(): array
+    {
+        $visibleElements = $this->getActiveElements();
+        if (empty($visibleElements['datafields'])) {
+            return [];
+        }
+
+        $datafields = DataField::getDataFields('sem');
+        $config = $GLOBALS['user']->cfg->ADMIN_COURSES_DATAFIELDS_FILTERS;
 
-        // get courses only if institutes available
-        $this->actions = $this->getActions();
+        $datafields = array_filter($datafields, function (Datafield $datafield) use ($visibleElements, $config) {
+            return in_array($datafield->id, $visibleElements['datafields'])
+                && isset($config[$datafield->id]);
+        });
 
-        $config_my_course_type_filter = $GLOBALS['user']->cfg->MY_COURSES_TYPE_FILTER;
+        $result = [];
+        foreach ($datafields as $datafield) {
+            $result["df_{$datafield->id}"] = $config[$datafield->id];
+        }
+        return $result;
+    }
 
-        // Get the view filter
-        $this->view_filter = $this->getFilterConfig();
+    public function search_action()
+    {
+        $activeSidebarElements = $this->getActiveElements();
+        if (Request::get('search')) {
+            $GLOBALS['user']->cfg->store('ADMIN_COURSES_SEARCHTEXT', Request::get('search'));
+        } else {
+            $GLOBALS['user']->cfg->delete('ADMIN_COURSES_SEARCHTEXT');
+        }
+        if (Request::option('institut_id') && Request::option('institut_id') !== 'all') {
+            $GLOBALS['user']->cfg->store('MY_INSTITUTES_DEFAULT', Request::option('institut_id'));
+        } else {
+            $GLOBALS['user']->cfg->delete('MY_INSTITUTES_DEFAULT');
+        }
 
-        if (Request::get('sortFlag')) {
-            $GLOBALS['user']->cfg->store('MEINE_SEMINARE_SORT_FLAG', Request::get('sortFlag') === 'asc' ? 'DESC' : 'ASC');
+        if (Request::option('semester_id')) {
+            $GLOBALS['user']->cfg->store('MY_COURSES_SELECTED_CYCLE', Request::option('semester_id'));
+        } else {
+            $GLOBALS['user']->cfg->delete('MY_COURSES_SELECTED_CYCLE');
         }
-        if (Request::option('sortby')) {
-            $GLOBALS['user']->cfg->store('MEINE_SEMINARE_SORT', Request::option('sortby'));
+
+        if (Request::option('course_type') && Request::option('course_type') !== 'all') {
+            $GLOBALS['user']->cfg->store('MY_COURSES_TYPE_FILTER', Request::option('course_type'));
+        } else {
+            $GLOBALS['user']->cfg->delete('MY_COURSES_TYPE_FILTER');
         }
 
-        $this->selected_action = $GLOBALS['user']->cfg->MY_COURSES_ACTION_AREA;
-        if (is_null($this->selected_action) || (!is_numeric($this->selected_action) && !class_exists($this->selected_action))) {
-            $this->selected_action = 1;
+        if (Request::option('stgteil')) {
+            $GLOBALS['user']->cfg->store('MY_COURSES_SELECTED_STGTEIL', Request::option('stgteil'));
+        } else {
+            $GLOBALS['user']->cfg->delete('MY_COURSES_SELECTED_STGTEIL');
         }
 
-        $this->sortby = $GLOBALS['user']->cfg->MEINE_SEMINARE_SORT;
-        $this->sortFlag = $GLOBALS['user']->cfg->MEINE_SEMINARE_SORT_FLAG ?: 'ASC';
+        if (Request::option('teacher_filter')) {
+            $GLOBALS['user']->cfg->store('ADMIN_COURSES_TEACHERFILTER', Request::option('teacher_filter'));
+        } else {
+            $GLOBALS['user']->cfg->delete('ADMIN_COURSES_TEACHERFILTER');
+        }
 
-        $this->courses = $this->getCourses([
-            'sortby'      => $this->sortby,
-            'sortFlag'    => $this->sortFlag,
-            'view_filter' => $this->view_filter,
-            'typeFilter'  => $config_my_course_type_filter,
-            'datafields' => $this->getDatafieldFilters()
-        ], Request::get('display') === 'all');
+        $datafields_filters = $GLOBALS['user']->cfg->ADMIN_COURSES_DATAFIELDS_FILTERS;
+        foreach (DataField::getDataFields('sem') as $datafield) {
+            if (
+                Request::get('df_'.$datafield->getId())
+                && in_array($datafield->getId(), $activeSidebarElements['datafields'])
+            ) {
+                $datafields_filters[$datafield->getId()] = Request::get('df_'.$datafield->getId());
+            } else {
+                unset($datafields_filters[$datafield->getId()]);
+            }
+        }
+        $GLOBALS['user']->cfg->store('ADMIN_COURSES_DATAFIELDS_FILTERS', $datafields_filters);
 
-        if (in_array('contents', $this->view_filter)) {
-            $this->nav_elements = MyRealmModel::calc_nav_elements([$this->courses]);
+        $filter = AdminCourseFilter::get();
+        if (Request::option('course_id')) { //we have only one course and want to see if that course is part of the result set
+            $filter->query->where('course_id', 'seminare.Seminar_id = :course_id', ['course_id' => Request::option('course_id')]);
         }
-        // get all available teacher for infobox-filter
-        // filter by selected teacher
-        $_SESSION['MY_COURSES_LIST'] = array_map(function ($c, $id) {
-        return [
-            'Name' => $c['Name'],
-            'Seminar_id' => $id
+        $count = $filter->countCourses();
+        if ($count > $this->max_show_courses && !Request::submitted('without_limit')) {
+            $this->render_json([
+                'count' => $count
+            ]);
+            return;
+        }
+        $courses = AdminCourseFilter::get()->getCourses();
+
+        $data = [
+            'data' => []
         ];
-        }, array_values($this->courses), array_keys($this->courses));
+        if (Request::submitted('activated_fields')) {
+            $GLOBALS['user']->cfg->store('MY_COURSES_ADMIN_VIEW_FILTER_ARGS', json_encode(Request::getArray('activated_fields')));
+        }
+        $activated_fields = $this->getFilterConfig();
+
+        $GLOBALS['user']->cfg->store('MY_COURSES_ACTION_AREA', Request::option('action'));
+        foreach ($courses as $course) {
+            if ($course->parent_course && !Request::option('course_id')) {
+                continue;
+            }
+            $data['data'][] = $this->getCourseData($course, $activated_fields);
+            foreach ($course->children as $childcourse) {
+                $data['data'][] = $this->getCourseData($childcourse, $activated_fields);
+            }
+        }
+        $tf = new Flexi_TemplateFactory($GLOBALS['STUDIP_BASE_PATH'] . '/app/views');
+        switch ($GLOBALS['user']->cfg->MY_COURSES_ACTION_AREA) {
+            case 1:
+            case 2:
+            case 3:
+            case 4:
+                break;
+            case 8: //Sperrebenen
+                $template = $tf->open('admin/courses/lock_preselect');
+                $template->course = $course;
+                $template->all_lock_rules = new SimpleCollection(array_merge(
+                    [[
+                        'name'    => '--' . _('keine Sperrebene') . '--',
+                        'lock_id' => 'none'
+                    ]],
+                    LockRule::findAllByType('sem')
+                ));
+                $data['buttons_top'] = $template->render();
+                $data['buttons_bottom'] = (string) \Studip\Button::createAccept(_('Sperrebenen'), 'locking_button', ['formaction' => URLHelper::getURL('dispatch.php/admin/courses/set_lockrule')]);
+                break;
+            case 9: //Sichtbarkeit
+                $data['buttons_top'] = '<label>'._('Alle auswählen').'<input type="checkbox" data-proxyfor=".course-admin td:last-child :checkbox"></label>';
+                $data['buttons_bottom'] = (string) \Studip\Button::createAccept(_('Sichtbarkeit'), 'visibility_button', ['formaction' => URLHelper::getURL('dispatch.php/admin/courses/set_visibility')]);
+                break;
+            case 10: //Zusatzangaben
+                $template = $tf->open('admin/courses/aux_preselect');
+                $template->course = $course;
+                $template->aux_lock_rules = AuxLockRule::findBySQL('1 ORDER BY name ASC');
+                $data['buttons_top'] = $template->render();
+                $data['buttons_bottom'] = (string) \Studip\Button::createAccept(_('Zusatzangaben'), 'aux_button', ['formaction' => URLHelper::getURL('dispatch.php/admin/courses/set_aux_lockrule')]);
+                break;
+            case 11: //Veranstaltung kopieren
+                break;
+            case 14: //Zugangsberechtigungen
+                break;
+            case 16: //Löschen
+                $data['buttons_top'] = '<label>'._('Alle auswählen').'<input type="checkbox" data-proxyfor=".course-admin td:last-child :checkbox"></label>';
+                $data['buttons_bottom'] = (string) \Studip\Button::createAccept(_('Löschen'), 'deleting_button', ['formaction' => URLHelper::getURL('dispatch.php/course/archive/confirm')]);
+                break;
+            case 17: //Gesperrte Veranstaltungen
+                $data['buttons_top'] = '<label>'._('Alle auswählen').'<input type="checkbox" data-proxyfor=".course-admin td:last-child :checkbox"></label>';
+                $data['buttons_bottom'] = (string) \Studip\Button::createAccept(_('Einstellungen speichern'), 'locking_button', ['formaction' => URLHelper::getURL('dispatch.php/admin/courses/set_locked')]);
+                break;
+            case 18: //Startsemester
+                break;
+            case 19: //LV-Gruppen
+                break;
+            case 20: //Notiz
+                break;
+            default:
+                foreach (PluginManager::getInstance()->getPlugins('AdminCourseAction') as $plugin) {
+                    if ($GLOBALS['user']->cfg->MY_COURSES_ACTION_AREA === get_class($plugin)) {
+                        $multimode = $plugin->useMultimode();
+                        if ($multimode) {
+                            $data['buttons_top'] = '<label>'._('Alle auswählen').'<input type="checkbox" data-proxyfor=".course-admin td:last-child :checkbox"></label>';
+                            if ($multimode instanceof Flexi_Template) {
+                                $data['buttons_bottom'] = $multimode->render();
+                            } elseif (is_string($multimode)) {
+                                $data['buttons_bottom'] = (string) \Studip\Button::create($multimode, '', ['formaction' => $plugin->getAdminActionURL()]);
+                            } else {
+                                $data['buttons_bottom'] = (string) \Studip\Button::create(_('Speichern'), '', ['formaction' => $plugin->getAdminActionURL()]);
+                            }
+                        }
+                    }
+                    break;
+                }
+        }
+        if (!isset($data['buttons_top'])) {
+            $data['buttons_top'] = '';
+        }
+        if (!isset($data['buttons_bottom'])) {
+            $data['buttons_bottom'] = '';
+        }
 
+         $this->render_json($data);
+    }
 
-        $this->all_lock_rules = new SimpleCollection(array_merge(
-            [[
-                'name'    => '--' . _("keine Sperrebene") . '--',
-                'lock_id' => 'none'
-            ]],
-            LockRule::findAllByType('sem')
-        ));
-        $this->aux_lock_rules = AuxLockRule::findBySQL('1 ORDER BY name');
+    protected function getCourseData(Course $course, $activated_fields)
+    {
+        $d = [
+            'id' => $course->id,
+            'parent_course' => $course->parent_course
+        ];
+        if (in_array('name', $activated_fields)) {
+            $params = tooltip2(_('Veranstaltungsdetails anzeigen'));
+            $params['style'] = 'cursor: pointer';
+            $d['name'] = '<a href="'.URLHelper::getLink('seminar_main.php', ['auswahl' => $course->id]).'">'
+                . htmlReady($course->name)
+                .'</a> '
+                .'<a href="'.URLHelper::getLink('dispatch.php/course/details/index/'. $course->id).'" data-dialog><button class="undecorated">'.Icon::create('info-circle', Icon::ROLE_INACTIVE)->asImg($params).'</button></a> '
+                .(!$course->visible ? _('(versteckt)') : '');
+        }
+        if (in_array('number', $activated_fields)) {
+            $d['number'] = '<a href="'.URLHelper::getLink('seminar_main.php', ['auswahl' => $course->id]).'">'
+                .$course->veranstaltungsnummer
+                .'</a>';
+        }
+        if (in_array('avatar', $activated_fields)) {
+            $d['avatar'] = '<a href="'.URLHelper::getLink('seminar_main.php', ['auswahl' => $course->id]).'">'
+                .CourseAvatar::getAvatar($course->getId())->getImageTag(Avatar::SMALL, ['title' => $course->name])
+                ."</a>";
+        }
+        if (in_array('type', $activated_fields)) {
+            $semtype = $course->getSemType();
+            $d['type'] = $semtype['name'];
+        }
+        if (in_array('room_time', $activated_fields)) {
+            $d['room_time'] = Seminar::GetInstance($course->id)->getDatesHTML([
+                'show_room'   => true,
+            ]) ?: _('nicht angegeben');
+        }
+        if (in_array('semester', $activated_fields)) {
+            $d['semester'] = $course->semester_text;
+        }
+        if (in_array('institute', $activated_fields)) {
+            $d['institute'] = $course->home_institut ? $course->home_institut->name : $course->institute;
+        }
+        if (in_array('requests', $activated_fields)) {
+            $d['requests'] = '<a href="'.URLHelper::getLink('dispatch.php/course/room_requests', ['cid' => $course->id]).'">'.count($course->room_requests)."</a>";
+        }
+        if (in_array('teachers', $activated_fields)) {
+            $teachers = $this->getTeacher($course->id);
+            $teachers = array_map(function ($teacher) {
+                return '<a href="'.URLHelper::getLink('dispatch.php/profile', ['username' => $teacher['username']]) .'">'. htmlReady($teacher['fullname']).'</a>';
+            }, $teachers);
+            $d['teachers'] = implode(', ', $teachers);
+        }
+        if (in_array('members', $activated_fields)) {
+            $d['members'] = '<a href="'.URLHelper::getLink('dispatch.php/course/members', ['cid' => $course->id]).'">'
+                .$course->getNumParticipants()
+                .'</a>';
+        }
+        if (in_array('waiting', $activated_fields)) {
+            $d['waiting'] = '<a href="'.URLHelper::getLink('dispatch.php/course/members', ['cid' => $course->id]).'">'
+                .$course->getNumWaiting()
+                .'</a>';
+        }
+        if (in_array('preliminary', $activated_fields)) {
+            $d['preliminary'] = '<a href="'.URLHelper::getLink('dispatch.php/course/members', ['cid' => $course->id]).'">'
+                .$course->getNumPrelimParticipants()
+                .'</a>';
+        }
+        if (in_array('contents', $activated_fields)) {
+            $icons = [];
+            foreach ($course->tools as $tool) {
+                $module = $tool->getStudipModule();
+                if ($module) {
+                    $last_visit = object_get_visit($course->id, $module->getPluginId());
+                    $nav = $module->getIconNavigation($course->id, $last_visit, $GLOBALS['user']->id);
+                    if (isset($nav) && $nav->isVisible(true)) {
+                        $icons[] = $nav;
+                    }
+                }
+            }
+            $d['contents'] = '<div class="icons">
+                <ul class="my-courses-navigation">';
+
+            foreach ($icons as $icon) {
+                $d['contents'] .= '<li class="my-courses-navigation-item '. ($icon->getImage()->signalsAttention() ? 'my-courses-navigation-important' : '').'">
+                        <a href="'. URLHelper::getLink('seminar_main.php', ['auswahl' => $course->id, 'redirect_to' => $icon->getURL()]).'"'. ($icon->getTitle() ? ' title="'.htmlReady($icon->getTitle()).'"' : '') .'>
+                            '. $icon->getImage()->asImg(20) .'
+                        </a>
+                    </li>';
+            }
+            $d['contents'] .= '</ul></div>';
+        }
+        if (in_array('last_activity', $activated_fields)) {
+            $d['last_activity'] = date('%x', lastActivity($course->id));
+        }
 
+        foreach (PluginManager::getInstance()->getPlugins('AdminCourseContents') as $plugin) {
+            foreach ($plugin->adminAvailableContents() as $index => $label) {
+                if (in_array($plugin->getPluginId() . '_' . $index, $activated_fields)) {
+                    $content = $plugin->adminAreaGetCourseContent($course, $index);
+                    $d[$plugin->getPluginId()."_".$index] = $content instanceof Flexi_Template ? $content->render() : $content;
+                }
+            }
+        }
+        $tf = new Flexi_TemplateFactory($GLOBALS['STUDIP_BASE_PATH'].'/app/views');
 
-        //build the sidebar:
-        $this->buildSidebar($config_my_course_type_filter);
+        switch ($GLOBALS['user']->cfg->MY_COURSES_ACTION_AREA) {
+            case 1:
+                $d['action'] = (string) \Studip\LinkButton::create(
+                    _('Grunddaten'),
+                    URLHelper::getURL('dispatch.php/course/basicdata/view', ['cid' => $course->id]),
+                    ['data-dialog' => '', 'role' => 'button']
+                );
+                break;
+            case 2:
+                $d['action'] = (string) \Studip\LinkButton::create(
+                    _('Studienbereiche'),
+                    URLHelper::getURL('dispatch.php/course/study_areas/show', ['cid' => $course->id, 'from' => 'admin/courses']),
+                    ['data-dialog' => '', 'role' => 'button']
+                );
+                break;
+            case 3:
+                $d['action'] = (string) \Studip\LinkButton::create(
+                    _('Zeiten/Räume'),
+                    URLHelper::getURL('dispatch.php/course/timesrooms/index', ['cid' => $course->id, 'cmd' => 'applyFilter']),
+                    ['data-dialog' => '', 'role' => 'button']
+                );
+                break;
+            case 4:
+                $d['action'] = (string) \Studip\LinkButton::create(
+                    _('Raumanfragen'),
+                    URLHelper::getURL('dispatch.php/course/room_requests/index', ['cid' => $course->id, 'origin' => 'admin_courses']),
+                    ['data-dialog' => '', 'role' => 'button']
+                );
+                break;
+            case 8: //Sperrebenen
+                $template = $tf->open('admin/courses/lock');
+                $template->course = $course;
+                $template->aux_lock_rules = AuxLockRule::findBySQL('1 ORDER BY name ASC');
+                $template->all_lock_rules = new SimpleCollection(array_merge(
+                    [[
+                        'name'    => '--' . _('keine Sperrebene') . '--',
+                        'lock_id' => 'none'
+                    ]],
+                    LockRule::findAllByType('sem')
+                ));
+                $d['action'] = $template->render();
+                break;
+            case 9: //Sichtbarkeit
+                $d['action'] = '<input type="hidden" name="all_sem[]" value="'.htmlReady($course->id).'"><input type="checkbox" name="visibility['.$course->id.']" '.($course->visible ? ' checked ' : '').'value="1">';
+                break;
+            case 10: //Zusatzangaben
+                $template = $tf->open('admin/courses/aux-select');
+                $template->course = $course;
+                $template->aux_lock_rules = AuxLockRule::findBySQL('1 ORDER BY name ASC');
+                $d['action'] = $template->render();
+                break;
+            case 11: //Veranstaltung kopieren
+                $d['action'] = (string) \Studip\LinkButton::create(
+                    _('Kopieren'),
+                    URLHelper::getURL('dispatch.php/course/wizard/copy/' . $course->id),
+                    ['data-dialog' => '', 'role' => 'button']
+                );
+                break;
+            case 14: //Zugangsberechtigungen
+                $d['action'] = (string) \Studip\LinkButton::create(
+                    _('Zugangsberechtigungen'),
+                    URLHelper::getURL('dispatch.php/course/admission', ['cid' => $course->id]),
+                    ['data-dialog' => '', 'role' => 'button']
+                );
+                break;
+            case 16: //Löschen
+                $d['action'] = '<input type="checkbox" name="archiv_sem[]" value="'.htmlReady($course->id).'" aria-label="'.htmlReady(sprintf(_('Veranstaltung %s löschen'), $course->getFullName())).'">';
+                break;
+            case 17: //Gesperrte Veranstaltungen
+                $cs = CourseSet::getSetForCourse($course->id);
+                if ($cs) {
+                    $locked = $cs->getId() === CourseSet::getGlobalLockedAdmissionSetId();
+                } else {
+                    $locked = false;
+                }
+                $d['action'] = '<input type="hidden" name="all_sem[]" value="'.htmlReady($course->id).'"><input type="checkbox" name="admission_locked['.$course->getId().']" '.($locked ? 'checked' : '').' value="1" aria-label="'.htmlReady(sprintf(_('Veranstaltung %s sperren'), $course->getFullName())).'">';
+                break;
+            case 18: //Startsemester
+                $d['action'] = (string) \Studip\LinkButton::create(
+                    _('Startsemester'),
+                    URLHelper::getURL('dispatch.php/course/timesrooms/editSemester', ['cid' => $course->id, 'origin' => 'admin_courses']),
+                    ['data-dialog' => '', 'role' => 'button']
+                );
+                break;
+            case 19: //LV-Gruppen
+                $d['action'] = (string) \Studip\LinkButton::create(
+                    _('LV-Gruppen'),
+                    URLHelper::getURL('dispatch.php/course/lvgselector', ['cid' => $course->id, 'from' => 'admin/courses']),
+                    ['data-dialog' => '', 'role' => 'button']
+                );
+                break;
+            case 20: //Notiz
+                $method = $course->config->COURSE_ADMIN_NOTICE ? 'createHasNotice' : 'createHasNoNotice';
+                $d['action'] = (string) \Studip\LinkButton::$method(
+                    _('Notiz'),
+                    URLHelper::getURL('dispatch.php/admin/courses/notice/'.$course->id),
+                    [
+                        'data-dialog' => 'size=auto',
+                        'title' => $course->config->COURSE_ADMIN_NOTICE,
+                        'role' => 'button'
+                    ]
+                );
+                break;
+            default:
+                foreach (PluginManager::getInstance()->getPlugins('AdminCourseAction') as $plugin) {
+                    if ($GLOBALS['user']->cfg->MY_COURSES_ACTION_AREA === get_class($plugin)) {
+                        $output = $plugin->getAdminCourseActionTemplate($course->getId());
+                        $d['action'] = $output instanceof Flexi_Template ? $output->render() : (string) $output;
+                    }
+                    break;
+                }
+        }
+        $d['completion'] = $course->completion;
+        return $d;
+    }
 
+    /**
+     * This action just stores the new settings for sorting the table of courses.
+     * @return void
+     */
+    public function sort_action()
+    {
+        if (Request::isPost()) {
+            $GLOBALS['user']->cfg->store('MEINE_SEMINARE_SORT', Request::get('sortby'));
+            $GLOBALS['user']->cfg->store('MEINE_SEMINARE_SORT_FLAG', Request::get('sortflag'));
+        }
+        $this->render_nothing();
     }
 
 
@@ -454,6 +820,18 @@ class Admin_CoursesController extends AuthenticatedController
         }
     }
 
+    public function get_stdgangteil_selector_action($institut_id)
+    {
+        $selector = $this->getStgteilSelector($institut_id);
+        $this->render_text($selector->render(['base_class' => 'sidebar']));
+    }
+
+    public function get_teacher_selector_action($institut_id)
+    {
+        $selector = $this->getTeacherWidget($institut_id);
+        $this->render_text($selector->render(['base_class' => 'sidebar']));
+    }
+
 
     /**
      * Export action
@@ -463,15 +841,7 @@ class Admin_CoursesController extends AuthenticatedController
         $filter_config = Request::getArray('fields');
 
         if (count($filter_config) > 0) {
-            $sortby = $GLOBALS['user']->cfg->getValue('MEINE_SEMINARE_SORT');
-            $config_my_course_type_filter = $GLOBALS['user']->cfg->getValue('MY_COURSES_TYPE_FILTER');
-
-            $courses = $this->getCourses([
-                'sortby' => $sortby,
-                'sortFlag' => 'asc',
-                'typeFilter' => $config_my_course_type_filter,
-                'view_filter' => $filter_config,
-            ], true);
+            $courses = AdminCourseFilter::get()->getCourses();
 
             $view_filters = $this->getViewFilters();
 
@@ -506,37 +876,40 @@ class Admin_CoursesController extends AuthenticatedController
                 }
 
                 if (in_array('requests', $filter_config)) {
-                    $row['requests'] = $course['requests'];
+                    $row['requests'] = $course['room_requests'];
                 }
 
                 if (in_array('teachers', $filter_config)) {
-                    $row['teachers'] = implode(', ', array_map(function ($d) {
-                        return $d['fullname'];
-                    }, $course['dozenten']));
+                    $row['teachers'] = implode(
+                        ', ',
+                        $course->teachers->map(function ($d) {
+                            return $d->user->getFullName();
+                        })
+                    );
                 }
 
                 if (in_array('members', $filter_config)) {
-                    $row['members'] = $course['teilnehmer'];
+                    $row['members'] = $course->getNumParticipants();
                 }
 
                 if (in_array('waiting', $filter_config)) {
-                    $row['waiting'] = $course['waiting'];
+                    $row['waiting'] = $course->getNumWaiting();
                 }
 
                 if (in_array('preliminary', $filter_config)) {
-                    $row['preliminary'] = $course['prelim'];
+                    $row['preliminary'] = $course->getNumPrelimParticipants();
                 }
 
                 if (in_array('last_activity', $filter_config)) {
-                    $row['last_activity'] = strftime('%x', $course['last_activity']);
+                    $row['last_activity'] = strftime('%x', lastActivity($course->id));
                 }
 
                 if (in_array('semester', $filter_config)) {
-                    $row['semester'] = $course_model->getTextualSemester();
+                    $row['semester'] = $course->getTextualSemester();
                 }
 
                 if (in_array('institute', $filter_config)) {
-                    $row['institute'] = $course_model->home_institut ? (string) $course_model->home_institut['name'] : $course_model['institut_id'];
+                    $row['institute'] = $course->home_institut ? (string) $course->home_institut['name'] : $course['institut_id'];
                 }
 
                 foreach (PluginManager::getInstance()->getPlugins('AdminCourseContents') as $plugin) {
@@ -551,7 +924,7 @@ class Admin_CoursesController extends AuthenticatedController
                     }
                 }
 
-                $data[$course_id] = $row;
+                $data[$course->id] = $row;
             }
 
             $captions = [];
@@ -581,51 +954,6 @@ class Admin_CoursesController extends AuthenticatedController
         }
     }
 
-    /**
-     * Set the selected institute or semester
-     */
-    public function set_selection_action()
-    {
-        if (Request::option('institute')) {
-            $GLOBALS['user']->cfg->store('ADMIN_COURSES_TEACHERFILTER', null);
-            $GLOBALS['user']->cfg->store('MY_COURSES_SELECTED_STGTEIL', null);
-            $inst = explode('_', Request::option('institute'));
-            $GLOBALS['user']->cfg->store('MY_INSTITUTES_DEFAULT', $inst[0]);
-
-            if (isset($inst[1]) && $inst[1] === 'withinst') {
-                $GLOBALS['user']->cfg->store('MY_INSTITUTES_INCLUDE_CHILDREN', 1);
-            } else {
-                $GLOBALS['user']->cfg->store('MY_INSTITUTES_INCLUDE_CHILDREN', 0);
-            }
-
-            PageLayout::postSuccess(_('Die gewünschte Einrichtung wurde ausgewählt!'));
-        }
-
-        if (Request::option('sem_select')) {
-            $GLOBALS['user']->cfg->store('MY_COURSES_SELECTED_CYCLE', Request::option('sem_select'));
-            if (Request::option('sem_select') !== "all") {
-                $sem_name = Semester::find(Request::option('sem_select'))->name;
-                PageLayout::postSuccess(sprintf(_('Das %s wurde ausgewählt'), htmlReady($sem_name)));
-            } else {
-                PageLayout::postSuccess(_('Semesterfilter abgewählt'));
-            }
-        }
-
-        if (Request::option('stgteil_select')) {
-            $GLOBALS['user']->cfg->store('MY_COURSES_SELECTED_STGTEIL', Request::option('stgteil_select'));
-            if (Request::option('stgteil_select') !== "all") {
-                PageLayout::postSuccess(sprintf(
-                    _('Der Studiengangteil %s wurde ausgewählt'),
-                    htmlReady(StudiengangTeil::find(Request::option('stgteil_select'))->getDisplayName())
-                ));
-            } else {
-                PageLayout::postSuccess(_('Studiengangteilfilter abgewählt'));
-            }
-        }
-
-        $this->redirect('admin/courses/index');
-    }
-
 
     /**
      * Set the lockrules of courses
@@ -785,9 +1113,9 @@ class Admin_CoursesController extends AuthenticatedController
                     // force to pre selection
                     if (Request::submitted('all')) {
                         $value = Request::get('lock_sem_all');
-                        $value_forced = Request::int('aux_all_forced');
+                        $value_forced = Request::int('aux_all_forced', 0);
                     } else {
-                        $value_forced = $lock_sem_forced[$course_id];
+                        $value_forced = $lock_sem_forced[$course_id] ?? 0;
                     }
 
                     $course = Course::find($course_id);
@@ -822,53 +1150,6 @@ class Admin_CoursesController extends AuthenticatedController
     }
 
 
-    /**
-     * Set the selected view filter and store the selection in configuration
-     */
-    public function set_view_filter_action($filter = null, $state = true)
-    {
-        // store view filter in configuration
-        if (!is_null($filter)) {
-            $filters = $this->getFilterConfig();
-
-            if ($state) {
-                $filters = array_diff($filters, [$filter]);
-            } else {
-                $filters[] = $filter;
-            }
-
-            $this->setFilterConfig($filters);
-        }
-
-        $this->redirect('admin/courses/index');
-    }
-
-    /**
-     * Set the selected action type and store the selection in configuration
-     */
-    public function set_action_type_action()
-    {
-        // select the action area
-        if (Request::option('action_area')) {
-            $GLOBALS['user']->cfg->store('MY_COURSES_ACTION_AREA', Request::option('action_area'));
-            PageLayout::postSuccess(_('Der Aktionsbereich wurde erfolgreich übernommen!'));
-        }
-
-        $this->redirect('admin/courses/index');
-    }
-
-    /**
-     * Set the selected course type filter and store the selection in configuration
-     */
-    public function set_course_type_action()
-    {
-        if (Request::option('course_type')) {
-            $GLOBALS['user']->cfg->store('MY_COURSES_TYPE_FILTER', Request::option('course_type'));
-            PageLayout::postSuccess(_('Der gewünschte Veranstaltungstyp wurde übernommen!'));
-        }
-        $this->redirect('admin/courses/index');
-    }
-
     /**
      * Marks a course as complete/incomplete.
      *
@@ -904,11 +1185,9 @@ class Admin_CoursesController extends AuthenticatedController
             $course->config->store('COURSE_ADMIN_NOTICE', trim(Request::get('notice')));
 
             if (Request::isXhr()) {
-                $this->response->add_header('X-Dialog-Notice', json_encode([
-                    'id'     => $course->id,
-                    'notice' => $course->config->COURSE_ADMIN_NOTICE,
-                ]));
-                $this->render_nothing();
+                $this->response->add_header('X-Dialog-Execute', 'STUDIP.AdminCourses.App.reloadCourse');
+                $this->response->add_header('X-Dialog-Close', '1');
+                $this->render_text($course->id);
             } else {
                 $this->redirect($this->indexURL("#course-{$course->id}"));
             }
@@ -919,30 +1198,6 @@ class Admin_CoursesController extends AuthenticatedController
         $this->notice = $course->config->COURSE_ADMIN_NOTICE;
     }
 
-    public function get_subcourses_action($course_id)
-    {
-        // get courses only if institutes available
-        $this->actions = $this->getActions();
-
-        // Get the view filter
-        $this->view_filter = $this->getFilterConfig();
-
-        $this->selected_action = $GLOBALS['user']->cfg->MY_COURSES_ACTION_AREA;
-        if (is_null($this->selected_action) || (!is_numeric($this->selected_action) && !class_exists($this->selected_action))) {
-            $this->selected_action = 1;
-        }
-
-        $this->courses = $this->getCourses([
-            'sortby'      => $this->sortby,
-            'sortFlag'    => $this->sortFlag,
-            'view_filter' => $this->view_filter,
-            'datafields' => $this->getDatafieldFilters(),
-            'parent_course' => $course_id
-        ]);
-
-        $this->parent = $course_id;
-
-    }
 
     /**
      * Return a specifically action or all available actions
@@ -1090,6 +1345,7 @@ class Admin_CoursesController extends AuthenticatedController
     private function getViewFilters()
     {
         $views = [
+            'avatar'        => _('Avatar'),
             'number'        => _('Nr.'),
             'name'          => _('Name'),
             'type'          => _('Veranstaltungstyp'),
@@ -1301,9 +1557,9 @@ class Admin_CoursesController extends AuthenticatedController
      * Adds view filter to the sidebar
      * @param array $configs
      */
-    private function setViewWidget($configs = [])
+    private function setViewWidget()
     {
-        $configs         = $configs ?: [];
+        $configs         = $this->getFilterConfig();
         $checkbox_widget = new OptionsWidget();
         $checkbox_widget->setTitle(_('Darstellungsfilter'));
 
@@ -1312,7 +1568,9 @@ class Admin_CoursesController extends AuthenticatedController
             $checkbox_widget->addCheckbox(
                 $label,
                 $state,
-                $this->url_for('admin/courses/set_view_filter/' . $index . '/' . $state)
+                $this->url_for('admin/courses/set_view_filter/' . $index . '/' . $state),
+                null,
+                ['onclick' => "$(this).toggleClass(['options-checked', 'options-unchecked']); $(this).attr('aria-checked', $(this).hasClass('options-checked') ? 'true' : 'false'); STUDIP.AdminCourses.App.toggleActiveField('".$index."'); return false;"]
             );
         }
         Sidebar::get()->addWidget($checkbox_widget, 'views');
@@ -1333,9 +1591,9 @@ class Admin_CoursesController extends AuthenticatedController
 
         if ($GLOBALS['perm']->have_perm('root') || (count($this->insts) > 1)) {
             $list->addElement(new SelectElement(
-                'all',
+                '',
                 $GLOBALS['perm']->have_perm('root') ? _('Alle') : _('Alle meine Einrichtungen'),
-                $GLOBALS['user']->cfg->MY_INSTITUTES_DEFAULT === 'all'),
+                !$GLOBALS['user']->cfg->MY_INSTITUTES_DEFAULT),
                 'select-all'
             );
         }
@@ -1366,6 +1624,7 @@ class Admin_CoursesController extends AuthenticatedController
                 );
             }
         }
+        $list->setOnSubmitHandler("STUDIP.AdminCourses.changeFiltersDependendOnInstitute($(this).find('select').val()); return false;");
 
         $sidebar->addWidget($list, 'filter_institute');
     }
@@ -1378,7 +1637,7 @@ class Admin_CoursesController extends AuthenticatedController
         $semesters = array_reverse(Semester::getAll());
         $sidebar = Sidebar::Get();
         $list = new SelectWidget(_('Semester'), $this->url_for('admin/courses/set_selection'), 'sem_select');
-        $list->addElement(new SelectElement('all', _('Alle')), 'sem_select-all');
+        $list->addElement(new SelectElement('', _('Alle')), 'sem_select-all');
         foreach ($semesters as $semester) {
             $list->addElement(new SelectElement(
                 $semester->id,
@@ -1386,6 +1645,7 @@ class Admin_CoursesController extends AuthenticatedController
                 $semester->id === $GLOBALS['user']->cfg->MY_COURSES_SELECTED_CYCLE
             ), 'sem_select-' . $semester->id);
         }
+        $list->setOnSubmitHandler("STUDIP.AdminCourses.App.changeFilter({semester_id: $(this).find('select').val()}); return false;");
 
         $sidebar->addWidget($list, 'filter_semester');
     }
@@ -1393,12 +1653,18 @@ class Admin_CoursesController extends AuthenticatedController
         /**
      * Adds the studiengangteil selector to the sidebar
      */
-    private function setStgteilSelector()
+    private function getStgteilSelector($institut_id = null)
     {
-        $stgteile = StudiengangTeil::getAllEnriched('fach_name','ASC', ['mvv_fach_inst.institut_id' => $GLOBALS['user']->cfg->MY_INSTITUTES_DEFAULT]);
-        $sidebar = Sidebar::Get();
+        $institut_id = $institut_id ?: $GLOBALS['user']->cfg->MY_INSTITUTES_DEFAULT;
+        $stgteile = StudiengangTeil::getAllEnriched('fach_name', 'ASC', ['mvv_fach_inst.institut_id' => $institut_id]);
         $list = new SelectWidget(_('Studiengangteil'), $this->url_for('admin/courses/set_selection'), 'stgteil_select');
-        $list->addElement(new SelectElement('all', _('Alle')), 'stgteil_select-all');
+        if (!$institut_id || $institut_id === 'all') {
+            $list->addElement(new SelectElement('', _('Wählen Sie eine Einrichtung') ), 'stgteil_select-all');
+        } elseif (count($stgteile) === 0) {
+                $list->addElement(new SelectElement('', _('Keine Studiengangteile zu der gewählten Einrichtung') ), 'stgteil_select-all');
+        } else {
+            $list->addElement(new SelectElement('', _('Alle')), 'stgteil_select-all');
+        }
         foreach ($stgteile as $stgteil) {
             $list->addElement(new SelectElement(
                 $stgteil->id,
@@ -1406,8 +1672,8 @@ class Admin_CoursesController extends AuthenticatedController
                 $stgteil->id === $GLOBALS['user']->cfg->MY_COURSES_SELECTED_STGTEIL
             ), 'stgteil_select-' . $stgteil->id);
         }
-
-        $sidebar->addWidget($list, 'filter_stgteil');
+        $list->setOnSubmitHandler("STUDIP.AdminCourses.App.changeFilter({stgteil: $(this).find('select').val()}); return false;");
+        return $list;
     }
 
 
@@ -1415,15 +1681,21 @@ class Admin_CoursesController extends AuthenticatedController
      * Adds HTML-Selector to the sidebar
      * @param null $selected_action
      */
-    private function setActionsWidget($selected_action = null)
+    private function setActionsWidget()
     {
         $actions = $this->getActions();
         $sidebar = Sidebar::Get();
         $list = new SelectWidget(_('Aktionsbereichauswahl'), $this->url_for('admin/courses/set_action_type'), 'action_area');
 
         foreach ($actions as $index => $action) {
-            $list->addElement(new SelectElement($index, $action['name'], $selected_action == $index), 'action-aria-' . $index);
+            $list->addElement(new SelectElement(
+                $index,
+                $action['name'],
+                $GLOBALS['user']->cfg->MY_COURSES_ACTION_AREA == $index),
+                'action-aria-' . $index
+            );
         }
+        $list->setOnSubmitHandler("STUDIP.AdminCourses.App.changeActionArea($(this).find('select').val()); return false;");
         $sidebar->addWidget($list, 'editmode');
     }
 
@@ -1433,12 +1705,12 @@ class Admin_CoursesController extends AuthenticatedController
      * @param string $selected
      * @param array $params
      */
-    private function setCourseTypeWidget($selected = 'all')
+    private function setCourseTypeWidget()
     {
         $sidebar = Sidebar::get();
         $this->url = $this->url_for('admin/courses/set_course_type');
         $this->types = [];
-        $this->selected = $selected;
+        $this->selected = $GLOBALS['user']->cfg->MY_COURSES_TYPE_FILTER;
 
         $list = new SelectWidget(
             _('Veranstaltungstypfilter'),
@@ -1446,7 +1718,7 @@ class Admin_CoursesController extends AuthenticatedController
             'course_type'
         );
         $list->addElement(new SelectElement(
-            'all', _('Alle'), $selected === 'all'
+            '', _('Alle'), !$GLOBALS['user']->cfg->MY_COURSES_TYPE_FILTER
         ), 'course-type-all');
         foreach ($GLOBALS['SEM_CLASS'] as $class_id => $class) {
             if ($class['studygroup_mode']) {
@@ -1456,7 +1728,7 @@ class Admin_CoursesController extends AuthenticatedController
             $element = new SelectElement(
                 $class_id,
                 $class['name'],
-                $selected === (string)$class_id
+                $GLOBALS['user']->cfg->MY_COURSES_TYPE_FILTER === (string)$class_id
             );
             $list->addElement(
                 $element->setAsHeader(),
@@ -1467,7 +1739,7 @@ class Admin_CoursesController extends AuthenticatedController
                 $element = new SelectElement(
                     $class_id . '_' . $id,
                     $result['name'],
-                    $selected === $class_id . '_' . $id
+                    $GLOBALS['user']->cfg->MY_COURSES_TYPE_FILTER === $class_id . '_' . $id
                 );
                 $list->addElement(
                     $element->setIndentLevel(1),
@@ -1475,6 +1747,7 @@ class Admin_CoursesController extends AuthenticatedController
                 );
             }
         }
+        $list->setOnSubmitHandler("STUDIP.AdminCourses.App.changeFilter({course_type: $(this).find('select').val()}); return false;");
         $sidebar->addWidget($list, 'filter-course-type');
     }
 
@@ -1482,35 +1755,38 @@ class Admin_CoursesController extends AuthenticatedController
      * Returns a widget to selected a specific teacher
      * @param array $teachers
      */
-    private function setTeacherWidget()
+    private function getTeacherWidget($institut_id = null)
     {
-        if (!$GLOBALS['user']->cfg->MY_INSTITUTES_DEFAULT || $GLOBALS['user']->cfg->MY_INSTITUTES_DEFAULT === "all") {
-            return;
-        }
+        $institut_id = $institut_id ?: $GLOBALS['user']->cfg->MY_INSTITUTES_DEFAULT;
         $teachers = DBManager::get()->fetchAll("
-            SELECT auth_user_md5.*, user_info.*
-            FROM auth_user_md5
-                LEFT JOIN user_info ON (auth_user_md5.user_id = user_info.user_id)
-                INNER JOIN user_inst ON (user_inst.user_id = auth_user_md5.user_id)
-                INNER JOIN Institute ON (Institute.Institut_id = user_inst.Institut_id)
-            WHERE (Institute.Institut_id = :institut_id OR Institute.fakultaets_id = :institut_id)
-                AND auth_user_md5.perms = 'dozent'
-            ORDER BY auth_user_md5.Nachname ASC, auth_user_md5.Vorname ASC
-        ", [
-            'institut_id' => $GLOBALS['user']->cfg->MY_INSTITUTES_DEFAULT
-        ],
-        function ($data) {
-            $ret['user_id'] = $data['user_id'];
-            unset($data['user_id']);
-            $ret['fullname'] = User::build($data)->getFullName("full_rev");
-            return $ret;
-        }
+                SELECT auth_user_md5.*, user_info.*
+                FROM auth_user_md5
+                    LEFT JOIN user_info ON (auth_user_md5.user_id = user_info.user_id)
+                    INNER JOIN user_inst ON (user_inst.user_id = auth_user_md5.user_id)
+                    INNER JOIN Institute ON (Institute.Institut_id = user_inst.Institut_id)
+                WHERE (Institute.Institut_id = :institut_id OR Institute.fakultaets_id = :institut_id)
+                    AND auth_user_md5.perms = 'dozent'
+                ORDER BY auth_user_md5.Nachname ASC, auth_user_md5.Vorname ASC
+            ", [
+                'institut_id' => $institut_id
+            ],
+            function ($data) {
+                $ret['user_id'] = $data['user_id'];
+                unset($data['user_id']);
+                $ret['fullname'] = User::build($data)->getFullName("full_rev");
+                return $ret;
+            }
         );
 
 
-        $sidebar = Sidebar::Get();
         $list = new SelectWidget(_('Lehrendenfilter'), $this->url_for('admin/courses/index'), 'teacher_filter');
-        $list->addElement(new SelectElement('all', _('alle'), Request::get('teacher_filter') == 'all'), 'teacher_filter-all');
+        if (!$institut_id || $institut_id === 'all') {
+            $list->addElement(new SelectElement('', _('Wählen Sie eine Einrichtung') ), 'teacher_filter-all');
+        } elseif (count($teachers) === 0) {
+            $list->addElement(new SelectElement('', _('Keine Lehrenden in der gewählten Einrichtung') ), 'teacher_filter-all');
+        } else {
+            $list->addElement(new SelectElement('', _('Alle')), 'teacher_filter-all');
+        }
 
         foreach ($teachers as $teacher) {
             $list->addElement(new SelectElement(
@@ -1519,8 +1795,8 @@ class Admin_CoursesController extends AuthenticatedController
                 $GLOBALS['user']->cfg->ADMIN_COURSES_TEACHERFILTER === $teacher['user_id']
             ), 'teacher_filter-' . $teacher['user_id']);
         }
-
-        $sidebar->addWidget($list, 'filter_teacher');
+        $list->setOnSubmitHandler("STUDIP.AdminCourses.App.changeFilter({teacher_filter: $(this).find('select').val()}); return false;");
+        return $list;
     }
 
     /**
@@ -1530,7 +1806,15 @@ class Admin_CoursesController extends AuthenticatedController
     {
         $sidebar = Sidebar::Get();
         $search = new SearchWidget(URLHelper::getURL('dispatch.php/admin/courses'));
-        $search->addNeedle(_('Freie Suche'), 'search', true, null, null, $GLOBALS['user']->cfg->ADMIN_COURSES_SEARCHTEXT);
+        $search->addNeedle(
+            _('Freie Suche'),
+            'search',
+            true,
+            null,
+            '',
+            $GLOBALS['user']->cfg->ADMIN_COURSES_SEARCHTEXT
+        );
+        $search->setOnSubmitHandler("STUDIP.AdminCourses.App.changeFilter({search: $(this).find('input').val()}); return false;");
         $sidebar->addWidget($search, 'filter_search');
     }
 
@@ -1556,7 +1840,9 @@ class Admin_CoursesController extends AuthenticatedController
         }
 
         if (!$config) {
-            $config = $this->setFilterConfig($available_filters);
+            $config = $this->setFilterConfig([
+                'number', 'name', 'semester', 'institute', 'teachers'
+            ]);
         }
 
         return $config;
diff --git a/app/controllers/course/basicdata.php b/app/controllers/course/basicdata.php
index baa817a0fd515fe85915d27774452a76b0fc1117..3bf10f730a6f88e7640d63686ca4a1125befae5c 100644
--- a/app/controllers/course/basicdata.php
+++ b/app/controllers/course/basicdata.php
@@ -524,7 +524,14 @@ class Course_BasicdataController extends AuthenticatedController
         }
         $this->flash['msg'] = $this->msg;
         $this->flash['open'] = Request::get("open");
-        $this->redirect($this->url_for('course/basicdata/view/' . $sem->getId()));
+        if (Request::isDialog()) {
+            $this->response->add_header('X-Dialog-Close', 1);
+            $this->response->add_header('X-Dialog-Execute', 'STUDIP.AdminCourses.App.reloadCourse');
+            $this->render_text($course_id);
+        } else {
+            $this->redirect($this->url_for('course/basicdata/view/' . $sem->getId()));
+        }
+
     }
 
     public function add_member_action($course_id, $status = 'dozent')
diff --git a/app/controllers/course/lvgselector.php b/app/controllers/course/lvgselector.php
index 101bee0e042ebd1b31f5d4dd2f680abf086cfb3f..60b090e81b7e741ae31077c16e4f348cb9dff8f2 100644
--- a/app/controllers/course/lvgselector.php
+++ b/app/controllers/course/lvgselector.php
@@ -196,8 +196,13 @@ class Course_LvgselectorController extends AuthenticatedController
             $url = $this->action_url('index');
         }
 
-
-        $this->redirect($url);
+        if (Request::isDialog()) {
+            $this->response->add_header('X-Dialog-Close', 1);
+            $this->response->add_header('X-Dialog-Execute', 'STUDIP.AdminCourses.App.reloadCourse');
+            $this->render_text($this->course_id);
+        } else {
+            $this->redirect($url);
+        }
     }
 
     /**
diff --git a/app/controllers/course/study_areas.php b/app/controllers/course/study_areas.php
index 3643309d4bc74d565d43ceb5114ff2eb4675c258..2993f569636a990b929fed99e4cb60c9dda56e1d 100644
--- a/app/controllers/course/study_areas.php
+++ b/app/controllers/course/study_areas.php
@@ -149,7 +149,13 @@ class Course_StudyAreasController extends AuthenticatedController
         } else {
             PageLayout::postError($msg);
         }
-        $this->redirect($url);
+        if (Request::isDialog()) {
+            $this->response->add_header('X-Dialog-Close', 1);
+            $this->response->add_header('X-Dialog-Execute', 'STUDIP.AdminCourses.App.reloadCourse');
+            $this->render_text($this->course->id);
+        } else {
+            $this->redirect($url);
+        }
     }
 
     public function unassign()
diff --git a/app/controllers/course/timesrooms.php b/app/controllers/course/timesrooms.php
index 7e3d4596d4febb92e9ff40bda9f2c8d1e0c17cb8..7168bd7c99f5c2a6db8eb4386a8b1ba43dd3703b 100644
--- a/app/controllers/course/timesrooms.php
+++ b/app/controllers/course/timesrooms.php
@@ -245,6 +245,9 @@ class Course_TimesroomsController extends AuthenticatedController
             $this->checked_dates = $_SESSION['_checked_dates'];
             unset($_SESSION['_checked_dates']);
         }
+        if (Request::isDialog()) {
+            $this->response->add_header('X-Dialog-Execute', '{"func": "STUDIP.AdminCourses.App.reloadCourse", "payload": "'.$this->course->getId().'"}');
+        }
     }
 
     /**
@@ -314,6 +317,9 @@ class Course_TimesroomsController extends AuthenticatedController
                 }
                 $this->relocate(str_replace('_', '/', Request::option('origin')));
             }
+            if (Request::isDialog()) {
+                $this->response->add_header('X-Dialog-Execute', '{"func": "STUDIP.AdminCourses.App.reloadCourse", "payload": "'.$this->course->getId().'"}');
+            }
         }
     }
 
diff --git a/app/controllers/resources/room_request.php b/app/controllers/resources/room_request.php
index e2b476671d55e3c49e5ee475c53a20d91bf97cfc..03c65161229cf23249926d6c97c6ee2fc6fc8d88 100644
--- a/app/controllers/resources/room_request.php
+++ b/app/controllers/resources/room_request.php
@@ -1997,6 +1997,8 @@ class Resources_RoomRequestController extends AuthenticatedController
         if ($save_only) {
             // redirect to reload all infos and showing the most current ones
             $this->redirect('resources/room_request/resolve/' . $request_id);
+        } elseif (Request::isDialog()) {
+            $this->response->add_header('X-Dialog-Execute', '{"func": "STUDIP.AdminCourses.App.reloadCourse", "payload": "'.Context::get()->id.'"}');
         }
     }
 
diff --git a/app/views/admin/courses/_course.php b/app/views/admin/courses/_course.php
deleted file mode 100644
index e12d31a16d3c60d102fb82019d458677ea4eb12a..0000000000000000000000000000000000000000
--- a/app/views/admin/courses/_course.php
+++ /dev/null
@@ -1,227 +0,0 @@
-<?php
-/**
- * Show course only if it has no parent course or the parent course is not
- * part of the current view. Otherwise the current course will be listed
- * as subcourse under its parent.
- *
- * @var array $values
- * @var array $courses
- * @var string $semid
- * @var string $parent
- * @var Admin_CoursesController $controller
- * @var array $view_filter
- * @var Semester $semester
- * @var string $selected_action
- */
-if (!$values['parent_course'] || !in_array($values['parent_course'], array_keys($courses))) : ?>
-    <?php
-    $course = Course::find($semid);
-    $children = [];
-    if ($GLOBALS['SEM_CLASS'][$GLOBALS['SEM_TYPE'][$values['status']]['class']]['is_group']) {
-        $children = Course::findbyParent_Course($semid);
-    }
-    ?>
-    <tr id="course-<?= $semid ?>"<?= $parent ? ' class="subcourses subcourse-' . $parent . '"' : '' ?> data-course-id="<?= $semid ?>">
-        <td>
-        <? if (Config::get()->ADMIN_COURSES_SHOW_COMPLETE): ?>
-            <? if ($GLOBALS['perm']->have_studip_perm('tutor', $semid)) : ?>
-                <a href="<?= $controller->toggle_complete($course) ?>"
-                   class="course-completion"
-                   data-course-completion="<?= $values['completion'] ?>"
-                   title="<?= htmlReady($course->getCompetionLabel()) ?>"
-                   aria-label="<?= _('Bearbeitungsstatus ändern') ?>">
-                    <?= _('Bearbeitungsstatus ändern') ?>
-                </a>
-            <? else : ?>
-                <?= $course->getCompletionIcon()->asImg(['title' => _('Bearbeitungsstatus kann nicht von Ihnen geändert werden.')]) ?>
-            <? endif ?>
-        <? else: ?>
-            <?= CourseAvatar::getAvatar($semid)->getImageTag(Avatar::SMALL, ['title' => trim($values['Name'])]) ?>
-        <? endif; ?>
-        </td>
-        <? if (in_array('number', $view_filter)) : ?>
-            <td>
-                <? if ($GLOBALS['perm']->have_studip_perm('autor', $semid)) : ?>
-                <a href="<?= URLHelper::getLink('seminar_main.php', ['auswahl' => $semid]) ?>">
-                    <? endif ?>
-                    <?= htmlReady($values["VeranstaltungsNummer"]) ?>
-                    <? if ($GLOBALS['perm']->have_studip_perm('autor', $semid)) : ?>
-                </a>
-            <? endif ?>
-            </td>
-        <? endif ?>
-        <? if (in_array('name', $view_filter)) : ?>
-            <td>
-                <? if ($GLOBALS['perm']->have_studip_perm("autor", $semid)) : ?>
-                <a href="<?= URLHelper::getLink('seminar_main.php', ['auswahl' => $semid]) ?>">
-                    <? endif ?>
-                    <?= htmlReady($course->name) ?>
-                    <? if ($GLOBALS['perm']->have_studip_perm("autor", $semid)) : ?>
-                </a>
-            <? endif ?>
-                <a data-dialog="buttons=false" href="<?= $controller->url_for(sprintf('course/details/index/%s', $semid)) ?>">
-                    <? $params = tooltip2(_("Veranstaltungsdetails anzeigen")); ?>
-                    <? $params['style'] = 'cursor: pointer'; ?>
-                    <?= Icon::create('info-circle', 'inactive')->asImg($params) ?>
-                </a>
-                <? if ($values["visible"] == 0) : ?>
-                    <?= _("(versteckt)") ?>
-                <? endif ?>
-                <?php if (count($children) > 0) : ?>
-                    <br>
-                    <a href="" class="toggle-subcourses" data-get-subcourses-url="<?= $controller->url_for('admin/courses/get_subcourses', $semid) ?>">
-                        <?= Icon::create('add', 'clickable')->asImg(12) ?>
-                        <?= Icon::create('remove', 'clickable', ['class' => 'hidden-js'])->asImg(12) ?>
-                        <?= sprintf(
-                            ngettext('%u Unterveranstaltung', '%u Unterveranstaltungen',
-                                count($children)),
-                            count($children)) ?>
-                    </a>
-                <?php endif ?>
-            </td>
-        <? endif ?>
-        <? if (in_array('type', $view_filter)) : ?>
-            <td>
-                <?= htmlReady($GLOBALS['SEM_CLASS'][$GLOBALS['SEM_TYPE'][$values["status"]]["class"]]['name']) ?>:
-                <strong><?= htmlReady($GLOBALS['SEM_TYPE'][$values["status"]]["name"]) ?></strong>
-            </td>
-        <? endif ?>
-        <? if (in_array('room_time', $view_filter)) : ?>
-            <td class="raumzeit">
-                <?= Seminar::GetInstance($semid)->getDatesHTML([
-                    'semester_id' => $semester ? $semester->id : null,
-                    'show_room'   => true,
-                ]) ?: _('nicht angegeben') ?>
-            </td>
-        <? endif ?>
-        <? if (in_array('semester', $view_filter)) : ?>
-            <td>
-                <?= htmlReady($course->semester_text) ?>
-            </td>
-        <? endif?>
-        <? if (in_array('institute', $view_filter)) : ?>
-            <td>
-                <?= htmlReady($course->home_institut ? $course->home_institut['name'] : $course['institute']) ?>
-            </td>
-        <? endif?>
-        <? if (in_array('requests', $view_filter)) : ?>
-            <td style="text-align: center;">
-                <a title="<?=_('Raumanfragen')?>" href="<?= URLHelper::getLink('dispatch.php/course/room_requests', ['cid' => $semid])?>">
-                    <?= $values['requests'] ?>
-                </a>
-            </td>
-        <? endif ?>
-        <? if (in_array('teachers', $view_filter)) : ?>
-            <td>
-                <?= $this->render_partial_collection('my_courses/_dozent', $values['dozenten']) ?>
-
-            </td>
-        <? endif ?>
-        <? if (in_array('members', $view_filter)) : ?>
-            <td style="text-align: center;">
-                <a title="<?=_('Teilnehmende')?>" href="<?= URLHelper::getLink(count($children) > 0 ? 'dispatch.php/course/grouping/members' : 'dispatch.php/course/members', ['cid' => $semid]) ?>">
-                    <?= $values["teilnehmer"] ?>
-                </a>
-            </td>
-        <? endif ?>
-        <? if (in_array('waiting', $view_filter)) : ?>
-            <td style="text-align: center;">
-                <a title="<?=_('Teilnehmende auf der Warteliste')?>" href="<?= URLHelper::getLink('dispatch.php/course/members', ['cid' => $semid])?>">
-                    <?= $values["waiting"] ?>
-                </a>
-            </td>
-        <? endif ?>
-        <? if (in_array('preliminary', $view_filter)) : ?>
-            <td style="text-align: center;">
-                <a title="<?=_('Vorläufige Anmeldungen') ?>" href="<?= URLHelper::getLink('dispatch.php/course/members', ['cid' => $semid])?>">
-                    <?= $values['prelim'] ?>
-                </a>
-            </td>
-        <? endif ?>
-        <? if (in_array('contents', $view_filter)) : ?>
-            <td style="text-align: left; white-space: nowrap;">
-            <? if (!empty($values['navigation'])) : ?>
-                <ul class="my-courses-navigation" style="flex-wrap: nowrap">
-                <? foreach (MyRealmModel::array_rtrim($values['navigation']) as $key => $nav)  : ?>
-                    <? if ($nav instanceof Navigation && $nav->isVisible(true)) : ?>
-                        <li class="my-courses-navigation-item <? if ($nav->getImage()->signalsAttention()) echo 'my-courses-navigation-important'; ?>">
-                            <a href="<?=
-                            URLHelper::getLink('seminar_main.php',
-                                ['auswahl'     => $semid,
-                                    'redirect_to' => $nav->getURL()]) ?>" <?= $nav->hasBadgeNumber() ? 'class="badge" data-badge-number="' . intval($nav->getBadgeNumber()) . '"' : '' ?>>
-                                <?= $nav->getImage()->asImg(20, $nav->getLinkAttributes()) ?>
-                            </a>
-                        </li>
-                    <? elseif (is_string($key)) : ?>
-                        <li class="my-courses-navigation-item">
-                            <span class="empty-slot" style="width: 20px"></span>
-                        </li>
-                    <? endif ?>
-                <? endforeach ?>
-                </ul>
-            <? endif ?>
-            </td>
-        <? endif ?>
-        <? if (in_array('last_activity', $view_filter)) : ?>
-            <td style="text-align: center;">
-                <span title="<?=_('Datum der letzten Aktivität in dieser Veranstaltung')?>">
-                    <?= htmlReady(date('d.m.Y', $values['last_activity'])); ?>
-                </span>
-            </td>
-        <? endif ?>
-        <? foreach (PluginManager::getInstance()->getPlugins("AdminCourseContents") as $plugin) : ?>
-            <? foreach ($plugin->adminAvailableContents() as $index => $label) : ?>
-                <? if (in_array($plugin->getPluginId()."_".$index, $view_filter)) : ?>
-                    <td style="text-align: center;">
-                        <? $content = $plugin->adminAreaGetCourseContent($course, $index) ?>
-                        <?= is_a($content, "Flexi_Template") ? $content->render() : $content ?>
-                    </td>
-                <? endif ?>
-            <? endforeach ?>
-        <? endforeach ?>
-        <td class="actions">
-            <? if (isset($actions[$selected_action]['partial']) && is_numeric($selected_action) && $GLOBALS['perm']->have_studip_perm('tutor', $semid)) : ?>
-                <?= $this->render_partial("admin/courses/{$actions[$selected_action]['partial']}", [
-                    'course' => $course,
-                    'values' => $values,
-                    'action' => $actions[$selected_action],
-                ]) ?>
-            <? elseif (!is_numeric($selected_action)) : ?>
-                <? $plugin = PluginManager::getInstance()->getPlugin($selected_action) ?>
-                <? $template = $plugin->getAdminCourseActionTemplate($semid, $values) ?>
-                <? if ($template) : ?>
-                    <?= $template->render() ?>
-                <? elseif ($GLOBALS['perm']->have_studip_perm('tutor', $semid)) : ?>
-                    <?=
-                    \Studip\LinkButton::create(
-                        $actions[$selected_action]['title'],
-                        URLHelper::getURL(sprintf($actions[$selected_action]['url'], $semid),
-                            ($actions[$selected_action]['params'] ? $actions[$selected_action]['params'] : [])),
-                        ($actions[$selected_action]['attributes'] ? $actions[$selected_action]['attributes'] : [])
-                    ) ?>
-                <? endif ?>
-            <? elseif ($GLOBALS['perm']->have_studip_perm('tutor', $semid)) : ?>
-                <? $lockrules = [
-                    '2' => "sem_tree",
-                    '3' => "room_time",
-                    '11' => "seminar_copy",
-                    '14' => "admission_type",
-                    '16' => "seminar_archive",
-                    '17' => "admission_type",
-                    '18' => 'room_time'
-                ] ?>
-                <? if ($GLOBALS['perm']->have_studip_perm("admin", $semid) || !isset($lockrules[$selected_action]) || !LockRules::Check($semid, $lockrules[$selected_action])) : ?>
-                    <?=
-                    \Studip\LinkButton::create(
-                        $actions[$selected_action]['title'],
-                        URLHelper::getURL(
-                            sprintf($actions[$selected_action]['url'], $semid),
-                            $actions[$selected_action]['params'] ?? []
-                        ),
-                        $actions[$selected_action]['attributes'] ?? []
-                    ) ?>
-                <? endif ?>
-            <? endif ?>
-        </td>
-    </tr>
-<?php endif ?>
diff --git a/app/views/admin/courses/aux-select.php b/app/views/admin/courses/aux-select.php
index 950120e081f9062c782d90d617c1823934654f43..0b87029279fd43f9802508c71516660209e07a1d 100644
--- a/app/views/admin/courses/aux-select.php
+++ b/app/views/admin/courses/aux-select.php
@@ -10,7 +10,7 @@
         --<?= _('keine Zusatzangaben') ?>--
     </option>
 <? foreach ($aux_lock_rules as $rule) : ?>
-    <option value="<?= htmlReady($rule->id) ?>" <? if ($values['aux_lock_rule'] === $rule->id) echo 'selected'; ?>>
+    <option value="<?= htmlReady($rule->id) ?>" <? if ($course->aux_lock_rule === $rule->id) echo 'selected'; ?>>
         <?= htmlReady($rule->name) ?>
     </option>
 <? endforeach ?>
@@ -18,6 +18,6 @@
 <br>
 <label>
     <input type="checkbox" value="1" name="lock_sem_forced[<?= htmlReady($course->id) ?>]"
-           <?= $values['aux_lock_rule_forced'] ? 'checked' : '' ?>>
+           <?= $course->aux_lock_rule_forced ? 'checked' : '' ?>>
     <?=_('Erzwungen')?>
 </label>
diff --git a/app/views/admin/courses/aux_preselect.php b/app/views/admin/courses/aux_preselect.php
index 73a2036577e970c1d0451011a85345bb1c3db60f..d7fd801eb2aefaf26df7454829cc79d79c637565 100644
--- a/app/views/admin/courses/aux_preselect.php
+++ b/app/views/admin/courses/aux_preselect.php
@@ -21,4 +21,4 @@
     <input type="checkbox" value="1" name="aux_all_forced">
     <?=_('Erzwungen')?>
 </label>
-<?= \Studip\Button::createAccept(_('Speichern'), 'all'); ?>
+<?= \Studip\Button::createAccept(_('Speichern'), 'all', ['formaction' => URLHelper::getURL('dispatch.php/admin/courses/set_aux_lockrule')]); ?>
diff --git a/app/views/admin/courses/get_subcourses.php b/app/views/admin/courses/get_subcourses.php
deleted file mode 100644
index b2857ae7f45914932a0fb17386b1cbe4229ef2b4..0000000000000000000000000000000000000000
--- a/app/views/admin/courses/get_subcourses.php
+++ /dev/null
@@ -1,12 +0,0 @@
-<?php
-/**
- * @var array $courses
- * @var array $view_filter
- * @var array $actions
- * @var string $selected_action
- * @var string $parent
- */
-?>
-<?php foreach ($courses as $semid => $values) : ?>
-    <?= $this->render_partial('admin/courses/_course', compact('semid', 'values', 'view_filter', 'actions', 'selected_action', 'parent', 'courses')) ?>
-<?php endforeach;
diff --git a/app/views/admin/courses/index.php b/app/views/admin/courses/index.php
index 7145332b37f67035fc4b6b3b7f844fb1da613fc5..812790c615167d7b284fda551a21093367dc067e 100644
--- a/app/views/admin/courses/index.php
+++ b/app/views/admin/courses/index.php
@@ -2,19 +2,44 @@
 /**
  * @var Admin_CoursesController $controller
  * @var int $count_courses
+ * @var Semester $semester
+ * @var array $fields
+ * @var array $activated_fields
+ * @var string $sortby
+ * @var string $sortflag
+ * @var array $activeSidebarElements
+ * @var int $max_show_courses
  */
+
+$unsortable_fields = [
+    'avatar',
+    'room_time',
+    'contents'
+];
 ?>
+
 <? if (empty($insts)): ?>
     <?= MessageBox::info(sprintf(_('Sie wurden noch keinen Einrichtungen zugeordnet. Bitte wenden Sie sich an einen der zuständigen %sAdministratoren%s.'), '<a href="' . URLHelper::getLink('dispatch.php/siteinfo/show') . '">', '</a>')) ?>
-<? elseif (!empty($courses)): ?>
-    <?= $this->render_partial('admin/courses/courses.php', compact('courses')) ?>
-<? elseif ($count_courses): ?>
-    <?= MessageBox::info(sprintf(
-        _('Es wurden %u Veranstaltungen gefunden. Grenzen Sie das Suchergebnis mit den Filtermöglichkeiten weiter ein, oder %slassen Sie sich alle Veranstaltungen anzeigen%s.'),
-        $count_courses,
-        '<a href="' . $controller->url_for('admin/courses', ['display' => 'all']) . '">',
-        '</a>'
-    )) ?>
-<? else: ?>
-    <?= MessageBox::info(_('Ihre Suche ergab keine Treffer')) ?>
+<? else :
+
+    $attributes = [
+        ':show-complete' => json_encode((bool) Config::get()->ADMIN_COURSES_SHOW_COMPLETE),
+        ':fields' => json_encode($fields),
+        ':unsortable-fields' => json_encode($unsortable_fields),
+        ':max-courses' => (int) $max_show_courses,
+        'sort-by' => $sortby,
+        'sort-flag' => $sortflag,
+    ];
+?>
+    <form method="post">
+        <?= CSRFProtection::tokenTag() ?>
+
+        <div class="admin-courses-vue-app course-admin"
+             is="AdminCourses"
+             v-cloak
+             ref="app"
+             <?= arrayToHtmlAttributes($attributes) ?>
+        ></div>
+    </form>
+
 <? endif; ?>
diff --git a/app/views/admin/courses/lock.php b/app/views/admin/courses/lock.php
index 459b8f744fdad80fb1b545cea1de2b175f8bdc03..d364f89bc22e6827c975e85863f77d0abb1b475b 100644
--- a/app/views/admin/courses/lock.php
+++ b/app/views/admin/courses/lock.php
@@ -5,13 +5,13 @@
  * @var Course $course
  */
 ?>
-<? $current_lock_rule = $all_lock_rules->findOneBy('lock_id', $values['lock_rule']); ?>
+<? $current_lock_rule = $all_lock_rules->findOneBy('lock_id', $course->lock_rule); ?>
 <? if (!$GLOBALS['perm']->have_perm('root') && ($current_lock_rule['permission'] == 'admin' || $current_lock_rule['permission'] == 'root')) : ?>
     <?= htmlReady($current_lock_rule['name'])?>
 <? else : ?>
     <select name="lock_sem[<?= htmlReady($course->id) ?>]" style="max-width: 200px">
     <? foreach ($all_lock_rules as $lock_rule): ?>
-        <option value="<?= $lock_rule['lock_id'] ?>" <?= $lock_rule['lock_id'] == $values['lock_rule'] ?  'selected' : '' ?>>
+        <option value="<?= $lock_rule['lock_id'] ?>" <?= $lock_rule['lock_id'] === $course->lock_rule ?  'selected' : '' ?>>
             <?= htmlReady($lock_rule['name']) ?>
         </option>
     <? endforeach; ?>
diff --git a/app/views/admin/courses/lock_preselect.php b/app/views/admin/courses/lock_preselect.php
index b9bfcef73fa04c6ce2d075ab5e4a04d44e28d6ea..433230e73ae1993d7335bd7f87423f2b25eff0d9 100644
--- a/app/views/admin/courses/lock_preselect.php
+++ b/app/views/admin/courses/lock_preselect.php
@@ -1,6 +1,6 @@
 <?php
 /**
- * @var array $values
+ * @var Course $course
  * @var SimpleCollection $all_lock_rules
  */
 ?>
@@ -8,11 +8,11 @@
     <select name="lock_sem_all" style="max-width: 200px">
         <? for ($i = 0; $i < count($all_lock_rules); $i++) : ?>
             <option value="<?= $all_lock_rules[$i]["lock_id"] ?>"
-                <?= ($all_lock_rules[$i]["lock_id"] == $values['lock_rule']) ? 'selected' : '' ?>>
+                <?= $all_lock_rules[$i]['lock_id'] === $course->lock_rule ? 'selected' : '' ?>>
                 <?= htmlReady($all_lock_rules[$i]["name"]) ?>
             </option>
         <? endfor ?>
     </select>
 </label>
 
-<?= \Studip\Button::createAccept(_('Zuweisen'), 'all'); ?>
+<?= \Studip\Button::createAccept(_('Zuweisen'), 'all', ['formaction' => URLHelper::getURL('dispatch.php/admin/courses/set_lockrule')]); ?>
diff --git a/app/views/admin/courses/sidebar.php b/app/views/admin/courses/sidebar.php
index 92263f1946b7fbb603cc851fc8625a5cdf55befd..6b243caa42806b51258de36015185f934ba7c534 100644
--- a/app/views/admin/courses/sidebar.php
+++ b/app/views/admin/courses/sidebar.php
@@ -17,16 +17,16 @@
         </label>
 
         <label>
-            <input name="instituteActive" type="checkbox" value="1"
-                <?= (!empty($userSelectedElements['institute'])) ? 'checked' : '' ?>
+            <input name="semesterActive" type="checkbox" value="1"
+                <?= !empty($userSelectedElements['semester']) ? 'checked' : '' ?>
                 >
-            <?= _('Einrichtung'); ?>
+            <?= _('Semester'); ?>
         </label>
         <label>
-            <input name="semesterActive" type="checkbox" value="1"
-                <?= (!empty($userSelectedElements['semester'])) ? 'checked' : '' ?>
+            <input name="instituteActive" type="checkbox" value="1"
+                <?= !empty($userSelectedElements['institute']) ? 'checked' : '' ?>
                 >
-            <?= _('Semester'); ?>
+            <?= _('Einrichtung'); ?>
         </label>
         <label>
             <input name="stgteilActive" type="checkbox" value="1"
@@ -34,18 +34,18 @@
                 >
             <?= _('Studiengangteil'); ?>
         </label>
-        <label>
-            <input name="courseTypeActive" type="checkbox" value="1"
-                <?= (!empty($userSelectedElements['courseType'])) ? 'checked' : '' ?>
-                >
-            <?= _('Veranstaltungstypfilter'); ?>
-        </label>
         <label>
             <input name="teacherActive" type="checkbox" value="1"
                 <?= (!empty($userSelectedElements['teacher'])) ? 'checked' : '' ?>
-                >
+            >
             <?= _('Lehrperson'); ?>
         </label>
+        <label>
+            <input name="courseTypeActive" type="checkbox" value="1"
+                <?= !empty($userSelectedElements['courseType']) ? 'checked' : '' ?>
+                >
+            <?= _('Veranstaltungstypfilter'); ?>
+        </label>
         <label>
             <input name="viewFilterActive" type="checkbox" value="1"
                 <?= (!empty($userSelectedElements['viewFilter'])) ? 'checked' : '' ?>
diff --git a/app/views/course/lvgselector/index.php b/app/views/course/lvgselector/index.php
index ca9b46f14735c67f4828aeef49b7b796858c5ddb..925c1c4844efa3afc16e3a94abd94e2f191cb2ca 100644
--- a/app/views/course/lvgselector/index.php
+++ b/app/views/course/lvgselector/index.php
@@ -1,5 +1,7 @@
 <? if (!$locked) : ?>
-    <form action="<?= $controller->link_for('course/lvgselector/index/' . $course_id, $url_params ?? []) ?>" method="post">
+    <form action="<?= $controller->link_for('course/lvgselector/index/' . $course_id, $url_params ?? []) ?>"
+          <?= Request::isDialog() ? 'data-dialog' : '' ?>
+          method="post">
 <? endif ?>
 <h1><?= _('Lehrveranstaltungsgruppen') ?></h1>
 <div id="assigned" data-ajax-url="<?= $ajax_url ?>" data-forward-url="<?= $no_js_url ?>">
diff --git a/app/views/course/study_areas/show.php b/app/views/course/study_areas/show.php
index efec1b45025f976c0c0a39ffefb036cc84b568dc..5bf7a5af0460a0bd2774617f7200c3b5b63176b1 100644
--- a/app/views/course/study_areas/show.php
+++ b/app/views/course/study_areas/show.php
@@ -1,5 +1,7 @@
 <? if (!$locked) : ?>
-    <form action="<?= $controller->url_for('course/study_areas/save/' . $course->id, $url_params) ?>" method="post">
+    <form action="<?= $controller->link_for('course/study_areas/save/' . $course->id, $url_params) ?>"
+          <?= Request::isDialog() ? 'data-dialog' : '' ?>
+          method="post">
 <? endif?>
     <?= $tree ?>
     <div style="text-align: center;">
diff --git a/db/migrations/5.4.9_add_datafields_filter_config.php b/db/migrations/5.4.9_add_datafields_filter_config.php
new file mode 100644
index 0000000000000000000000000000000000000000..b44d108cde703b5f4a17edc25e322ca378bab1c8
--- /dev/null
+++ b/db/migrations/5.4.9_add_datafields_filter_config.php
@@ -0,0 +1,26 @@
+<?php
+
+class AddDatafieldsFilterConfig extends Migration
+{
+    protected function up()
+    {
+        $query = "INSERT IGNORE INTO `config` (`field`, `value`, `type`, `range`, `mkdate`, `chdate`, `description`)
+                  VALUES (:name, :value, :type, :range, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), :description)";
+
+        $statement = DBManager::get()->prepare($query);
+        $statement->execute([
+            ':name'        => 'ADMIN_COURSES_DATAFIELDS_FILTERS',
+            ':description' => 'Für Admins, Roots und DedicatedAdmins können hier die Datenfelder gespeichert werden, nach denen die Veranstaltungen gefiltert werden sollen.',
+            ':range'       => 'user',
+            ':type'        => 'array',
+            ':value'       => '[]'
+        ]);
+    }
+
+    protected function down()
+    {
+        DBManager::get()->prepare('
+            DELETE FROM `config` WHERE `field` = "ADMIN_COURSES_DATAFIELDS_FILTERS"
+        ');
+    }
+}
diff --git a/lib/classes/AdminCourseFilter.class.php b/lib/classes/AdminCourseFilter.class.php
index 7ec1c7bf6db189c8aa6aed3c9c50bdb6607fa882..68b96d2a8709b489ed10a6b0e6307c07e91ae61a 100644
--- a/lib/classes/AdminCourseFilter.class.php
+++ b/lib/classes/AdminCourseFilter.class.php
@@ -18,39 +18,17 @@
  *     public function addLectureshipFilter($event, $filter)
  *     {
  *         if ($GLOBALS['user']->cfg->getValue("LECTURESHIP_FILTER")) {
- *             $filter->settings['query']['joins']['lehrauftrag'] = array(
- *                 'join' => "INNER JOIN",
- *                 'on' => "seminare.Seminar_id = lehrauftrag.seminar_id"
- *             );
+ *             $filter->query->join('lehrauftrag', 'seminare.Seminar_id = lehrauftrag.seminar_id');
  *         }
  *     }
  *
- * Within this method you alter the public $filter->settings array, because this array
- * describes entirely the big query for the admin-search. In our example above
- * we simple add an INNER JOIN to filter for the course having an entry in
- * the lehrauftrag table.
- *
- * Description of this array is as follows:
- *
- * $filter->settings['query']            : The main sql query as a prepared statement.
- * $filter->settings['query']['select']  : An assoc array. $filter->settings['query']['select']['Number_of_teachers'] = "COUNT(DISTINCT dozenten.user_id)"
- *                                         will select the result of COUNT as the variable Number_of_teachers.
- * $filter->settings['query']['joins']   : Example $filter->settings['query']['joins']['dozenten'] = array(
- *                                          'join' => "INNER JOIN", //default value, else use "LEFT JOIN"
- *                                          'table' => "seminar_user", //can me omitted if you don't want to use a table-alias
- *                                          'on' => "dozenten.Seminar_id = seminare.Seminar_id AND dozenten.status = 'dozent'"
- *                                          )
- *                                         if 'table' differs from the index, the index will be the alias of the table.
- *                                         So normally you don't need to name a table if you don't want it to be aliased.
- * $filter->settings['query']['where']   : You might want to use the method $filter->where($sql, $parameter) instead.
- * $filter->settings['query']['orderby'] : You might want to use $filter->orderBy($attribute, $flag = "ASC") instead.
- * $filter->settings['parameter']        : An assoc array of parameter that will be passed to
- *                                         the prepared statement.
+ * Within this method you alter the public $filter->query object. That query object is of type SQLQuery.
  *
  */
 class AdminCourseFilter
 {
     static protected $instance = null;
+    public $query = null;
     public $max_show_courses = 500;
     public $settings = [];
 
@@ -68,288 +46,131 @@ class AdminCourseFilter
     }
 
     /**
-     * Constructor of the singleton-object. The settings might come from the session
-     * if $reset_settings is false.
-     * @param bool $reset_settings : should the session settings of the singleton be reset?
-     */
-    public function __construct($reset_settings = false)
-    {
-        $this->initSettings();
-
-        if ($reset_settings) {
-            $this->resetSettings();
-        } else {
-            $this->restoreSettings();
-        }
-    }
-
-    /**
-     * store settings in session
-     */
-    public function storeSettings()
-    {
-        $_SESSION['AdminCourseFilter_settings'] = $this->settings;
-    }
-
-    /**
-     * restore settings from session
-     */
-    public function restoreSettings()
-    {
-        if ($_SESSION['AdminCourseFilter_settings']) {
-            $this->settings = $_SESSION['AdminCourseFilter_settings'];
-        }
-    }
-
-    /**
-     * reset settings
+     * Constructor of the singleton-object.
      */
-    public function resetSettings()
+    public function __construct()
     {
         $this->initSettings();
-        unset($_SESSION['AdminCourseFilter_settings']);
     }
 
-    /**
-     * initialize settings
-     */
-    public function initSettings()
-    {
-        $this->settings = [];
-
-        $this->settings['query']['select'] = [
-            'Institut' => "Institute.Name",
-            'teilnehmer' => "(SELECT COUNT(seminar_id)
-                          FROM seminar_user
-                          WHERE seminar_id = seminare.Seminar_id AND status != 'dozent' AND status != 'tutor')",
-            'prelim' => "(SELECT COUNT(seminar_id)
-                          FROM admission_seminar_user
-                          WHERE seminar_id = seminare.Seminar_id AND status = 'accepted')",
-            'waiting' => "(SELECT COUNT(seminar_id)
-                          FROM admission_seminar_user
-                          WHERE seminar_id = seminare.Seminar_id AND status = 'awaiting')",
-            'requests' => "(SELECT COUNT(id)
-                          FROM resource_requests
-                          WHERE course_id = seminare.Seminar_id)",
-            'course_set' => "(SELECT set_id FROM seminar_courseset WHERE seminar_id = seminare.Seminar_id LIMIT 1)"
-        ];
-        $this->settings['query']['joins'] = [
-            'seminar_inst' => [
-                'join' => "INNER JOIN",
-                'on' => "seminare.Seminar_id = seminar_inst.seminar_id"
-            ],
-            'Institute' => [
-                'join' => "INNER JOIN",
-                'on' => "seminar_inst.institut_id = Institute.Institut_id"
-            ],
-            'sem_types' => [
-                'join' => "LEFT JOIN",
-                'on' => "sem_types.id = seminare.status"
-            ],
-            'sem_classes' => [
-                'join' => "LEFT JOIN",
-                'on' => "sem_classes.id = sem_types.class"
-            ]
-        ];
-        $this->settings['query']['where'] = [];
-        $this->settings['query']['orderby'] = Config::get()->IMPORTANT_SEMNUMBER ? "seminare.veranstaltungsnummer, seminare.name" : "seminare.name";
-    }
-
-    /**
-     * Adds a filter for all courses of the given semester.
-     * @param string $semester_id : ID of the given semester.
-     * @return AdminCourseFilter
-     * @throws Exception if semester_id does not exist
-     */
-    public function filterBySemester($semester_id)
-    {
-        $semester = Semester::find($semester_id);
-        if (!$semester) {
-            throw new Exception("Das ausgewählte Semester scheint nicht zu existieren.");
-        }
-        $this->settings['query']['joins']['semester_courses'] = [
-            'join' => "LEFT JOIN",
-            'on' => "semester_courses.course_id = seminare.Seminar_id"
-        ];
-        $this->settings['query']['where']['semester'] = "(semester_courses.semester_id IS NULL OR semester_courses.semester_id = :semester_id)";
-        $this->settings['parameter']['semester_beginn'] = $semester['beginn'];
-        $this->settings['parameter']['semester_id'] = $semester['id'];
-        return $this;
-    }
-
-    /**
-     * Adds a filter for a sem_type or many sem_types if the parameter is an array.
-     * @param array|integer $type : id or ids of sem_types
-     * @return AdminCourseFilter
-     */
-    public function filterByType($type)
-    {
-        if (is_array($type)) {
-            $this->settings['query']['where']['status'] = "seminare.status IN (:types)";
-            $this->settings['parameter']['types'] = $type;
+    protected function initSettings()
+    {
+        $this->query = SQLQuery::table('seminare');
+        $this->query->join('sem_types', 'sem_types', 'sem_types.id = seminare.status');
+        $this->query->join('sem_classes', 'sem_classes', 'sem_classes.id = sem_types.class');
+        $this->query->where("sem_classes.studygroup_mode = '0'");
+        $this->query->groupBy('seminare.Seminar_id');
+
+        if ($GLOBALS['user']->cfg->ADMIN_COURSES_SEARCHTEXT) {
+            $this->query->join('teachers_su', 'seminar_user', "teachers_su.Seminar_id = seminare.Seminar_id AND teachers_su.status = 'dozent'");
+            $this->query->join('teachers', 'auth_user_md5', 'teachers.user_id = teachers_su.user_id');
+            $this->query->where(
+                'search',
+                "(seminare.name LIKE :search OR seminare.VeranstaltungsNummer LIKE :search OR seminare.untertitel LIKE :search OR CONCAT(teachers.Vorname, ' ', teachers.Nachname) LIKE :search)",
+                ['search' => '%'.$GLOBALS['user']->cfg->ADMIN_COURSES_SEARCHTEXT.'%']
+            );
+        }
+        if (Request::option('course_id')) {
+            $this->query->where('course_id', 'seminare.Seminar_id = :course_id', ['course_id' => Request::option('course_id')]);
+        }
+        $inst_ids = [];
+
+        if (
+            !$GLOBALS['user']->cfg->MY_INSTITUTES_DEFAULT
+            || $GLOBALS['user']->cfg->MY_INSTITUTES_DEFAULT === 'all'
+        ) {
+            $inst = new SimpleCollection(Institute::getMyInstitutes($GLOBALS['user']->id));
+            $inst_ids = $inst->map(function ($a) {
+                return $a['Institut_id'];
+            });
         } else {
-            $this->settings['query']['where']['status'] = "seminare.status = :type";
-            $this->settings['parameter']['type'] = (int) $type;
+            //We must check, if the institute ID belongs to a faculty
+            //and has the string _i appended to it.
+            //In that case we must display the courses of the faculty
+            //and all its institutes.
+            //Otherwise we just display the courses of the faculty.
+
+            $include_children = false;
+            $inst_id = $GLOBALS['user']->cfg->MY_INSTITUTES_DEFAULT;
+            if (str_contains($inst_id, '_')) {
+                $inst_id = substr($inst_id, 0, strpos($inst_id, '_'));
+                $include_children = true;
+            }
+            $inst_ids[] = $inst_id;
+
+            if ($include_children) {
+                $inst = Institute::find($inst_id);
+                if ($inst && $inst->isFaculty()) {
+                    foreach ($inst->sub_institutes->pluck('Institut_id') as $institut_id) {
+                        $inst_ids[] = $institut_id;
+                    }
+                }
+            }
         }
-        return $this;
-    }
 
-    /**
-     * Adds a filter for an institut_id or many institut_ids if the parameter is an array.
-     * @param array|integer $institut_ids : id or ids of institutes
-     * @return AdminCourseFilter
-     */
-    public function filterByInstitute($institut_ids)
-    {
         if (Config::get()->ALLOW_ADMIN_RELATED_INST) {
             $sem_inst = 'seminar_inst';
+            $this->query->join('seminar_inst', 'seminar_inst', 'seminar_inst.seminar_id = seminare.Seminar_id');
         } else {
             $sem_inst = 'seminare';
         }
 
-        if (is_array($institut_ids)) {
-            $this->settings['query']['where']['institute'] = "$sem_inst.institut_id IN (:institut_ids)";
-            $this->settings['parameter']['institut_ids'] = $institut_ids;
-        } else {
-            $this->settings['query']['where']['status'] = "$sem_inst.institut_id = :institut_id";
-            $this->settings['parameter']['institut_id'] = (string) $institut_ids;
+        $this->query->where('seminar_inst', "$sem_inst.institut_id IN (:institut_ids)");
+        $this->query->parameter('institut_ids', $inst_ids);
+
+        if ($GLOBALS['user']->cfg->MY_COURSES_SELECTED_CYCLE) {
+            $this->query->join('semester_courses', 'semester_courses.course_id = seminare.Seminar_id');
+            $this->query->where('semester_id', '(semester_courses.semester_id = :semester_id OR semester_courses.semester_id IS NULL)', [
+                'semester_id' => $GLOBALS['user']->cfg->MY_COURSES_SELECTED_CYCLE
+            ]);
         }
-        return $this;
-    }
 
-    /**
-     * Adds a filter for an stgteil_id or many stgteil_ids if the parameter is an array.
-     * @param array|integer $stgteil_ids : id or ids of stgteile
-     * @return AdminCourseFilter
-     */
-    public function filterByStgTeil($stgteil_ids)
-    {
-        $this->settings['query']['joins']['mvv_lvgruppe_seminar'] = [
-            'join' => "LEFT JOIN",
-            'table' => "mvv_lvgruppe_seminar",
-            'on' => "mvv_lvgruppe_seminar.seminar_id = seminare.Seminar_id"
-        ];
-        $this->settings['query']['joins']['mvv_lvgruppe_modulteil'] = [
-            'join' => "LEFT JOIN",
-            'table' => "mvv_lvgruppe_modulteil",
-            'on' => "mvv_lvgruppe_modulteil.lvgruppe_id = mvv_lvgruppe_seminar.lvgruppe_id"
-        ];
-        $this->settings['query']['joins']['mvv_modulteil'] = [
-            'join' => "LEFT JOIN",
-            'table' => "mvv_modulteil",
-            'on' => "mvv_modulteil.modulteil_id = mvv_lvgruppe_modulteil.modulteil_id"
-        ];
-        $this->settings['query']['joins']['mvv_stgteilabschnitt_modul'] = [
-            'join' => "LEFT JOIN",
-            'table' => "mvv_stgteilabschnitt_modul",
-            'on' => "mvv_stgteilabschnitt_modul.modul_id = mvv_modulteil.modul_id"
-        ];
-        $this->settings['query']['joins']['mvv_stgteilabschnitt'] = [
-            'join' => "LEFT JOIN",
-            'table' => "mvv_stgteilabschnitt",
-            'on' => "mvv_stgteilabschnitt.abschnitt_id = mvv_stgteilabschnitt_modul.abschnitt_id"
-        ];
-        $this->settings['query']['joins']['mvv_stgteilversion'] = [
-            'join' => "LEFT JOIN",
-            'table' => "mvv_stgteilversion",
-            'on' => "mvv_stgteilversion.version_id = mvv_stgteilabschnitt.version_id"
-        ];
+        if ($GLOBALS['user']->cfg->MY_COURSES_TYPE_FILTER && $GLOBALS['user']->cfg->MY_COURSES_TYPE_FILTER !== 'all') {
+            if (str_contains(Request::option('course_type'), '_')) {
+                list($sem_class_id, $sem_type_id) = explode('_', $GLOBALS['user']->cfg->MY_COURSES_TYPE_FILTER);
+                $this->query->where('course_type', 'seminare.status = :course_type', ['course_type' => $sem_type_id]);
+            } else {
+                //sem class
+                $this->query->where('course_class', 'sem_types.class = :course_class', [
+                    'course_class' => $GLOBALS['user']->cfg->MY_COURSES_TYPE_FILTER
+                ]);
+            }
 
-        if (is_array($stgteil_ids)) {
-            $this->settings['query']['where']['mvv_stgteilversion'] = "mvv_stgteilversion.stgteil_id IN (:stgteil_ids)";
-            $this->settings['parameter']['stgteil_ids'] = $stgteil_ids;
-        } else {
-            $this->settings['query']['where']['mvv_stgteilversion'] = "mvv_stgteilversion.stgteil_id = :stgteil_id";
-            $this->settings['parameter']['stgteil_id'] = (string) $stgteil_ids;
         }
-        return $this;
-    }
 
-    /**
-     * @param array|string $user_ids
-     * @return AdminCourseFilter
-     */
-    public function filterByDozent($user_ids)
-    {
-        $this->settings['query']['joins']['dozenten'] = [
-            'join' => "INNER JOIN",
-            'table' => "seminar_user",
-            'on' => "dozenten.Seminar_id = seminare.Seminar_id AND dozenten.status = 'dozent'"
-        ];
-        if (is_array($user_ids)) {
-            $this->settings['query']['where']['dozenten'] = "dozenten.user_id IN (:dozenten_ids)";
-            $this->settings['parameter']['dozenten_ids'] = $user_ids;
-        } else {
-            $this->settings['query']['where']['dozenten'] = "dozenten.user_id = :dozenten_id";
-            $this->settings['parameter']['dozenten_id'] = (string) $user_ids;
+        if ($GLOBALS['user']->cfg->MY_COURSES_SELECTED_STGTEIL) {
+            $this->query->join('mvv_lvgruppe_seminar', '`mvv_lvgruppe_seminar`.`seminar_id` = `seminare`.`Seminar_id`');
+            $this->query->join('mvv_lvgruppe_modulteil', '`mvv_lvgruppe_modulteil`.`lvgruppe_id` = `mvv_lvgruppe_seminar`.`lvgruppe_id`');
+            $this->query->join('mvv_modulteil', '`mvv_modulteil`.`modulteil_id` = `mvv_lvgruppe_modulteil`.`modulteil_id`');
+            $this->query->join('mvv_modul', '`mvv_modul`.`modul_id` = `mvv_modulteil`.`modul_id`');
+            $this->query->join('mvv_stgteilabschnitt_modul', '`mvv_stgteilabschnitt_modul`.`modul_id` = `mvv_modul`.`modul_id`');
+            $this->query->join('mvv_stgteilabschnitt', '`mvv_stgteilabschnitt`.`abschnitt_id` = `mvv_stgteilabschnitt_modul`.`abschnitt_id`');
+            $this->query->join('mvv_stgteilversion', '`mvv_stgteilversion`.`version_id` = `mvv_stgteilabschnitt`.`version_id`');
+            $this->query->where('stgteil', 'mvv_stgteilversion.stgteil_id = :stgteil_id', [
+                'stgteil_id' => $GLOBALS['user']->cfg->MY_COURSES_SELECTED_STGTEIL
+            ]);
         }
-        return $this;
-    }
 
-    /**
-     * Adds a filter for a textstring, that can be the coursenumber, the name of the course
-     * or the last name of one of the dozenten.
-     * @param string $text the searchstring
-     * @return AdminCourseFilter
-     */
-    public function filterBySearchstring($text)
-    {
-        $this->settings['query']['joins']['dozenten'] = [
-            'join' => "INNER JOIN",
-            'table' => "seminar_user",
-            'on' => "dozenten.Seminar_id = seminare.Seminar_id AND dozenten.status = 'dozent'"
-        ];
-        $this->settings['query']['joins']['dozentendata'] = [
-            'join' => "INNER JOIN",
-            'table' => "auth_user_md5",
-            'on' => "dozenten.user_id = dozentendata.user_id"
-        ];
-        $this->settings['query']['where']['search'] = "(CONCAT_WS(' ', seminare.VeranstaltungsNummer, seminare.name, seminare.Untertitel, dozentendata.Nachname) LIKE :search
-            OR CONCAT(dozentendata.Nachname, ', ', dozentendata.Vorname) LIKE :search
-            OR CONCAT_WS(' ', dozentendata.Vorname, dozentendata.Nachname) LIKE :search
-            OR dozentendata.Vorname LIKE :search
-            OR dozentendata.Nachname LIKE :search
-        )";
-        $this->settings['parameter']['search'] = "%".$text."%";
+        if ($GLOBALS['user']->cfg->ADMIN_COURSES_TEACHERFILTER) {
+            $this->query->join('teachers_su', 'seminar_user', "teachers_su.Seminar_id = seminare.Seminar_id AND teachers_su.status = 'dozent'");
+            $this->query->where(
+                'teacher_filter',
+                "teachers_su.user_id = :teacher_id",
+                ['teacher_id' => $GLOBALS['user']->cfg->ADMIN_COURSES_TEACHERFILTER]
+            );
+        }
 
-        return $this;
-    }
 
-    /**
-     * @param string $attribute : column, name of the column, yb whcih we should order the results
-     * @param string $flag : "ASC" or "DESC for ascending order or descending order,
-     * @return AdminCourseFilter
-     * @throws Exception if $flag does not exist
-     */
-    public function orderBy($attribute, $flag = 'ASC')
-    {
-        $flag = mb_strtoupper($flag);
-        if (!in_array($flag, words('ASC DESC'))) {
-            throw new Exception("Sortierreihenfolge undefiniert.");
-        }
-        if (in_array($attribute, words('VeranstaltungsNummer Name status teilnehmer waiting prelim requests completion start_time Institute.Name'))) {
-            $this->settings['query']['orderby'] = $attribute . ' ' . $flag;
-        }
-        return $this;
-    }
 
-    /**
-     * Adds a where filter.
-     * @param string $where any where condition like "sem_classes.overview = 'CoreOverview'"
-     * @param array $parameter an array of parameter that appear in the $where query.
-     * @param null|string $id an id of the where-query. Use this to possibly
-     *                          avoid double where conditions or allow deleting the condition
-     *                          by plugins if necessary. Can be omitted.
-     * @return AdminCourseFilter
-     */
-    public function where($where, $parameter = [], $id = null)
-    {
-        if (!$id) {
-            $id = md5($where);
+        $datafields_filters = $GLOBALS['user']->cfg->ADMIN_COURSES_DATAFIELDS_FILTERS;
+        foreach ($datafields_filters as $datafield_id => $value) {
+            $this->query->join('de_'.$datafield_id, 'datafields_entries', 'de_'.$datafield_id.'.range_id = seminare.Seminar_id AND `de_'.$datafield_id.'`.datafield_id = :de_'.$datafield_id.'_id');
+            $this->query->where('de_' . $datafield_id . '_contents', 'de_' . $datafield_id . '.`content` LIKE :de_' . $datafield_id . '_content',
+                [
+                    'de_' . $datafield_id . '_id' => $datafield_id,
+                    'de_' . $datafield_id . '_content' => '%' . $value . '%'
+                ]);
         }
-        $this->settings['query']['where'][$id] = $where;
-        $this->settings['parameter'] = array_merge((array)($this->settings['parameter'] ?? []), $parameter);
-        return $this;
     }
 
     /**
@@ -359,16 +180,10 @@ class AdminCourseFilter
      * Plugins may register at this event to fully alter this AdminCourseFilter-object and so the resultset.
      * @return array associative array with seminar_ids as keys and seminar-data-arrays as values.
      */
-    public function getCourses($grouped = true)
+    public function getCourses()
     {
         NotificationCenter::postNotification("AdminCourseFilterWillQuery", $this);
-        if (empty($this->settings['query']['where'])) {
-            return [];
-        }
-        $statement = DBManager::get()->prepare($this->createQuery());
-        $statement->execute($this->settings['parameter']);
-        $_SESSION['AdminCourseFilter_settings'] = $this->settings;
-        return $statement->fetchAll($grouped ? (PDO::FETCH_GROUP | PDO::FETCH_ASSOC) : PDO::FETCH_ASSOC);
+        return $this->query->fetchAll(Course::class);
     }
 
     /**
@@ -377,10 +192,7 @@ class AdminCourseFilter
     public function countCourses()
     {
         NotificationCenter::postNotification("AdminCourseFilterWillQuery", $this);
-        if (empty($this->settings['query']['where'])) {
-            return 0;
-        }
-        return (int)DBManager::get()->fetchColumn($this->createQuery(true), $this->settings['parameter']);
+        return $this->query->count();
     }
 
     /**
@@ -400,54 +212,10 @@ class AdminCourseFilter
             $order = 'seminare.veranstaltungsnummer, seminare.name';
         }
         if ($count_courses && $count_courses <= $this->max_show_courses) {
-            $settings = $this->settings;
-            $this->settings['query']['select'] = [];
-            $this->settings['query']['orderby'] = $order;
-            $ret = $this->getCourses(false);
-            $this->settings = $settings;
-            return $ret;
+            $this->query->orderBy($order);
+            return $this->getCourses();
         }
         return [];
     }
 
-    /**
-     * Creates the sql-query from the $this->settings['query']
-     * @param boolean $only_count : boolean
-     * @return string the big query
-     */
-    public function createQuery($only_count = false)
-    {
-        if ($only_count) {
-            $select_query = "COUNT(DISTINCT seminare.Seminar_id) ";
-        } else {
-            $select_query = "seminare.* ";
-            foreach ((array) $this->settings['query']['select'] as $alias => $select) {
-                $select_query .= ", ".$select." AS ".$alias." ";
-            }
-        }
-
-        $join_query = "";
-        foreach ((array) $this->settings['query']['joins'] as $alias => $joininfo) {
-            $table = isset($joininfo['table']) ? $joininfo['table']." AS ".$alias : $alias;
-            $on = isset($joininfo['on']) ? " ON (".$joininfo['on'].")" : "";
-            $join_query .= " ".(isset($joininfo['join']) ? $joininfo['join'] : "INNER JOIN")." ".$table.$on." ";
-        }
-
-        $where_query = "";
-        if (count($this->settings['query']['where']) > 0) {
-            $where_query .= implode(" AND ", $this->settings['query']['where']);
-        }
-
-        $query = "
-            SELECT ".$select_query."
-            FROM seminare
-                ".$join_query."
-            ".($where_query ? "WHERE ".$where_query : "");
-        if (!$only_count) {
-            $query .= " GROUP BY seminare.Seminar_id ORDER BY ".$this->settings['query']['orderby'].($this->settings['query']['orderby'] !== "seminare.name" ? ", seminare.name" : "");
-        }
-
-        return $query;
-    }
-
 }
diff --git a/lib/classes/SQLQuery.php b/lib/classes/SQLQuery.php
index 3a09fd9568b814b74254861c62939eb1788c1b12..0442604ff6e2933bd411c9c5e8ac58e56254234c 100644
--- a/lib/classes/SQLQuery.php
+++ b/lib/classes/SQLQuery.php
@@ -283,20 +283,20 @@ class SQLQuery
             $on = isset($joindata['on']) ? " ON ({$joindata['on']})" : '';
             $sql .= " " . (isset($joindata['join']) ? $joindata['join'] : 'INNER JOIN') . " {$table}{$on} ";
         }
-        if ($this->settings['where']) {
+        if (!empty($this->settings['where'])) {
             $sql .= "WHERE (" . implode(") AND (", $this->settings['where']) . ") ";
         }
-        if ($this->settings['groupby']) {
+        if (!empty($this->settings['groupby'])) {
             $sql .= "GROUP BY {$this->settings['groupby']} ";
         }
-        if ($this->settings['having']) {
+        if (!empty($this->settings['having'])) {
             $sql .= "HAVING (" . implode(") AND (", $this->settings['having']) . ") ";
         }
 
-        if ($this->settings['order']) {
+        if (!empty($this->settings['order'])) {
             $sql .= "ORDER BY {$this->settings['order']} ";
         }
-        if ($this->settings['limit']) {
+        if (!empty($this->settings['limit'])) {
             $sql .= "LIMIT ". (int) $this->settings['limit'][0] . ", " . (int) $this->settings['limit'][1] . " ";
         }
         return $sql;
diff --git a/lib/classes/sidebar/OptionsWidget.php b/lib/classes/sidebar/OptionsWidget.php
index 0cb805cef85965d76e94d724f269f92fbf1a6e21..a7931ce57852979e4fff1875e7dbb7d91ec0745b 100644
--- a/lib/classes/sidebar/OptionsWidget.php
+++ b/lib/classes/sidebar/OptionsWidget.php
@@ -34,8 +34,9 @@ class OptionsWidget extends ListWidget
         $toggle_url_off = isset($toggle_url_off) ? html_entity_decode($toggle_url_off) : null;
 
         $content = sprintf(
-            '<a href="%s" class="options-checkbox options-%s" %s>%s</a>',
+            '<a href="%s" role="checkbox" aria-checked="%s" class="options-checkbox options-%s" %s>%s</a>',
             htmlReady($state && $toggle_url_off !== null ? $toggle_url_off : $toggle_url),
+            $state ? 'true' : 'false',
             $state ? 'checked' : 'unchecked',
             arrayToHtmlAttributes($attributes),
             htmlReady($label)
diff --git a/lib/classes/sidebar/SearchWidget.php b/lib/classes/sidebar/SearchWidget.php
index 82aa26b8c9952b182e83b4a0365c0ce1feded0bb..16d577b62d8a15bc614f89de1bedf0eec6ad0f38 100644
--- a/lib/classes/sidebar/SearchWidget.php
+++ b/lib/classes/sidebar/SearchWidget.php
@@ -16,6 +16,7 @@ class SearchWidget extends SidebarWidget
     protected $filters = [];
     protected $method = 'get';
     protected $id = null;
+    protected $onsubmit = null;
 
     /**
      * Constructor for the widget.
@@ -105,6 +106,11 @@ class SearchWidget extends SidebarWidget
         return count($this->elements) + count($this->needles) + count($this->filters) > 0;
     }
 
+    public function setOnSubmitHandler($onsubmit)
+    {
+        $this->onsubmit = $onsubmit;
+    }
+
     /**
      * Renders the widget.
      *
@@ -167,6 +173,7 @@ class SearchWidget extends SidebarWidget
         $this->template_variables['filters'] = $this->filters;
 
         $this->template_variables['has_data'] = $this->hasData();
+        $this->template_variables['onsubmit'] = $this->onsubmit;
 
         return parent::render($variables);
     }
diff --git a/lib/classes/sidebar/SelectWidget.php b/lib/classes/sidebar/SelectWidget.php
index 73971757b8a70a466cfa5dafc64ba1d36fe6db1e..772004cb78b643d8cdab88165bb065d05edf35c0 100644
--- a/lib/classes/sidebar/SelectWidget.php
+++ b/lib/classes/sidebar/SelectWidget.php
@@ -8,6 +8,9 @@
  */
 class SelectWidget extends SidebarWidget
 {
+
+    protected $onsubmit = null;
+
     /**
      * Constructs the widget by defining a special template.
      *
@@ -125,6 +128,11 @@ class SelectWidget extends SidebarWidget
         }
     }
 
+    public function setOnSubmitHandler($onsubmit)
+    {
+        $this->onsubmit = $onsubmit;
+    }
+
     /**
      * Renders the select widget
      * @param  array  $variables Additional vaiarbles
@@ -141,6 +149,7 @@ class SelectWidget extends SidebarWidget
             }
             $this->template_variables['class'] .= ' submit-upon-select';
         }
+        $this->template_variables['onsubmit'] = $this->onsubmit;
 
         return parent::render($variables);
     }
diff --git a/lib/models/ConfigValue.php b/lib/models/ConfigValue.php
index 829567d4ea94bf17e2d3e55fb1093cf064d61bd7..f5410c93a0c22698227d67e039620abfe542ca8f 100644
--- a/lib/models/ConfigValue.php
+++ b/lib/models/ConfigValue.php
@@ -43,7 +43,7 @@ class ConfigValue extends SimpleORMap
         ];
 
         $config['registered_callbacks']['after_delete'][] = function (ConfigValue $value) {
-            if ($value->entry->type === 'i18n') {
+            if (isset($value->entry) && $value->entry->type === 'i18n') {
                 $value->getTypedValue()->removeTranslations();
             }
         };
diff --git a/lib/models/Course.class.php b/lib/models/Course.class.php
index 86e9a471c53bc796be826dfc3c1036a28e25e351..e16df06e5f33b9191a650b363348b823ba541451 100644
--- a/lib/models/Course.class.php
+++ b/lib/models/Course.class.php
@@ -224,6 +224,9 @@ class Course extends SimpleORMap implements Range, PrivacyObject, StudipItem, Fe
         $config['default_values']['schreibzugriff'] = 1;
         $config['default_values']['duration_time'] = 0;
 
+        $config['additional_fields']['teachers'] = [
+            'get' => 'getTeachers'
+        ];
         $config['additional_fields']['end_time'] = true;
 
         $config['additional_fields']['start_semester'] = [
@@ -456,6 +459,13 @@ class Course extends SimpleORMap implements Range, PrivacyObject, StudipItem, Fe
         }
     }
 
+    public function getTeachers()
+    {
+        return $this->members->filter(function ($m) {
+            return $m['status'] === 'dozent';
+        });
+    }
+
     public function getFreeSeats()
     {
         $free_seats = $this->admission_turnout - $this->getNumParticipants();
diff --git a/resources/assets/javascripts/bootstrap/admin-courses.js b/resources/assets/javascripts/bootstrap/admin-courses.js
index 3fa05110909e498b3786b6e53f72b7d61bf5bb89..06c4621ad636033d30f18a99668421a9e93d52ed 100644
--- a/resources/assets/javascripts/bootstrap/admin-courses.js
+++ b/resources/assets/javascripts/bootstrap/admin-courses.js
@@ -1,12 +1,25 @@
-STUDIP.Dialog.registerHeaderHandler('X-Dialog-Notice', json => {
-    json = JSON.parse(json);
+STUDIP.domReady(() => {
+    const node = document.querySelector('.admin-courses-vue-app');
+    if (!node) {
+        return;
+    }
 
-    $(`#course-${json.id} td.actions .button`)
-        .removeClass('has-notice has-no-notice')
-        .addClass(json.notice.length > 0 ? 'has-notice' : 'has-no-notice')
-        .attr('title', json.notice);
+    Promise.all([
+        STUDIP.Vue.load(),
+        import('../../../vue/store/AdminCoursesStore.js').then((config) => config.default),
+        import('../../../vue/components/AdminCourses.vue').then((component) => component.default),
+    ]).then(([{ createApp, store }, storeConfig, AdminCourses]) => {
+        store.registerModule('admincourses', storeConfig);
 
-    STUDIP.Dialog.close();
+        Object.entries(window.AdminCoursesStoreData ?? {}).forEach(([key, value]) => {
+            store.commit(`admincourses/${key}`, value);
+        })
 
-    return false;
+        const vm = createApp({
+            components: { AdminCourses },
+        });
+        vm.$mount(node);
+
+        STUDIP.AdminCourses.App = vm.$refs.app;
+    });
 });
diff --git a/resources/assets/javascripts/bootstrap/application.js b/resources/assets/javascripts/bootstrap/application.js
index d673455f73319768be1cede0b839d14c2fbc80e6..c6ffe3522808be2c8541798cc8dc30a78383d568 100644
--- a/resources/assets/javascripts/bootstrap/application.js
+++ b/resources/assets/javascripts/bootstrap/application.js
@@ -331,24 +331,6 @@ STUDIP.domReady(function () {
     }
 });
 
-jQuery(document).on('click', '.course-admin td .course-completion', function () {
-    var href    = $(this).attr('href'),
-        timeout = window.setTimeout(function () {
-            $(this).addClass('ajaxing');
-        }.bind(this), 300);
-
-    $.getJSON(href).done(function (response) {
-        clearTimeout(timeout);
-
-        $(this).removeClass('ajaxing').attr({
-            'data-course-completion': response.state,
-            title: response.label
-        });
-    }.bind(this));
-
-    return false;
-});
-
 // Global handler:
 // Toggle a table element. The url of the link will be called, an ajax
 // indicator will be shown instead of the element and the whole table row
diff --git a/resources/assets/javascripts/bootstrap/forms.js b/resources/assets/javascripts/bootstrap/forms.js
index 161b6d608af43e322f5d2644da9b91f54bb37e8c..3d9be08dc9083d848787b5c9e70ac03a60cdadb5 100644
--- a/resources/assets/javascripts/bootstrap/forms.js
+++ b/resources/assets/javascripts/bootstrap/forms.js
@@ -108,6 +108,7 @@ $(document)
         // select was opened by click
         if (!is_default && shouldSubmit) {
             $(this.form).submit();
+            $('option', this).prop('defaultSelected', false).filter(':selected').prop('defaultSelected', true);
             return false;
         }
     });
diff --git a/resources/assets/javascripts/init.js b/resources/assets/javascripts/init.js
index a7d6f05f96bc590df3470a8aba7cc3d2a59d39dd..55276026788cc9d5629de4eca1ec8611f85b03bd 100644
--- a/resources/assets/javascripts/init.js
+++ b/resources/assets/javascripts/init.js
@@ -4,6 +4,7 @@ import Vue from './lib/studip-vue.js';
 import ActionMenu from './lib/actionmenu.js';
 import ActivityFeed from './lib/activityfeed.js';
 import admin_sem_class from './lib/admin_sem_class.js';
+import AdminCourses from './lib/admin-courses.js';
 import Admission from './lib/admission.js';
 import Arbeitsgruppen from './lib/arbeitsgruppen.js';
 import Archive from './lib/archive.js';
@@ -66,6 +67,7 @@ import Resources from './lib/resources.js';
 import Responsive from './lib/responsive.js';
 import RESTAPI, { api } from './lib/restapi.js';
 import Schedule from './lib/schedule.js';
+import Screenreader from './lib/screenreader.js';
 import Scroll from './lib/scroll.js';
 import Search from './lib/search.js';
 import Sidebar from './lib/sidebar.js';
@@ -90,6 +92,7 @@ window.STUDIP = _.assign(window.STUDIP || {}, {
     ActionMenu,
     ActivityFeed,
     admin_sem_class,
+    AdminCourses,
     Admission,
     api,
     Arbeitsgruppen,
@@ -154,6 +157,7 @@ window.STUDIP = _.assign(window.STUDIP || {}, {
     RESTAPI,
     Schedule,
     Scroll,
+    Screenreader,
     Search,
     Sidebar,
     SkipLinks,
diff --git a/resources/assets/javascripts/lib/admin-courses.js b/resources/assets/javascripts/lib/admin-courses.js
new file mode 100644
index 0000000000000000000000000000000000000000..e6a3523db54835a9fbf559c2c80126b4049cc52a
--- /dev/null
+++ b/resources/assets/javascripts/lib/admin-courses.js
@@ -0,0 +1,20 @@
+const AdminCourses = {
+    App: null,
+    changeFiltersDependendOnInstitute(institut_id) {
+        STUDIP.AdminCourses.App.changeFilter({ institut_id });
+        //change Studiengangteil filter
+        $.get(
+            STUDIP.URLHelper.getURL('dispatch.php/admin/courses/get_stdgangteil_selector/' + institut_id)
+        ).done((widget) => {
+            $('select[name=stgteil_select]').closest('.sidebar-widget').replaceWith(widget);
+        });
+
+        //change Dozenten-Filter
+        $.get(
+            STUDIP.URLHelper.getURL('dispatch.php/admin/courses/get_teacher_selector/' + institut_id)
+        ).done((widget) => {
+            $('select[name=teacher_filter]').closest('.sidebar-widget').replaceWith(widget);
+        });
+    }
+};
+export default AdminCourses;
diff --git a/resources/assets/javascripts/lib/screenreader.js b/resources/assets/javascripts/lib/screenreader.js
new file mode 100644
index 0000000000000000000000000000000000000000..b8b9d098d468167a880a5e99d249f17075535e1b
--- /dev/null
+++ b/resources/assets/javascripts/lib/screenreader.js
@@ -0,0 +1,8 @@
+class Screenreader
+{
+    static notify(text) {
+        $('#notes_for_screenreader').text(text);
+    }
+}
+
+export default Screenreader;
diff --git a/resources/assets/stylesheets/less/tables.less b/resources/assets/stylesheets/less/tables.less
index c7b1f95c2627e4571919725d580eaf5214444ecc..eb576dd075bc97c3938163b32f1791c1569513dc 100644
--- a/resources/assets/stylesheets/less/tables.less
+++ b/resources/assets/stylesheets/less/tables.less
@@ -438,6 +438,23 @@ table.default {
             }
         }
     }
+    > tbody > tr.selected > td {
+        background-color: var(--yellow-20);
+        &:first-child {
+            position: relative;
+            &::before {
+                display: block;
+                content: '';
+                position: absolute;
+
+                top: 0;
+                bottom: 0;
+                left: 0;
+                width: 2px;
+                background-color: var(--light-gray-color);
+            }
+        }
+    }
     > tbody > tr.new > td {
         font-weight: bold;
         &:first-child {
@@ -465,6 +482,9 @@ table.default {
     &:not(.nohover) > tbody:not(.nohover) > tr:not(.nohover):hover > td:not(.nohover) {
         background-color: fadeout(@light-gray-color, 80%);
     }
+    &:not(.nohover) > tbody:not(.nohover) > tr.selected:not(.nohover):hover > td:not(.nohover) {
+        background-color: var(--yellow-40);
+    }
     > tfoot {
         > tr > td {
             background-color: @content-color-20;
diff --git a/resources/assets/stylesheets/scss/admin.scss b/resources/assets/stylesheets/scss/admin.scss
index a2e5843a06a37ea23f604c9e736f70e9ef5ac4bc..d8ac4d30d6c47cb61d4473b43186a291cda179fb 100644
--- a/resources/assets/stylesheets/scss/admin.scss
+++ b/resources/assets/stylesheets/scss/admin.scss
@@ -151,4 +151,7 @@ fieldset.attribute_table {
             background-image: url("#{$image-path}/loading-indicator.svg");
         }
     }
+    > tbody.loading > tr > td {
+        opacity: 0.5;
+    }
 }
diff --git a/resources/vue/components/AdminCourses.vue b/resources/vue/components/AdminCourses.vue
new file mode 100644
index 0000000000000000000000000000000000000000..8e8c5c84ea76a0bac0f79dd473227e9efe840b9f
--- /dev/null
+++ b/resources/vue/components/AdminCourses.vue
@@ -0,0 +1,265 @@
+<template>
+    <table class="default">
+        <caption>
+            {{ $gettext('Veranstaltungen') }}
+            <span class="actions" v-if="isLoading">
+                <img :src="loadingIndicator" width="20" height="20" :title="$gettext('Daten werden geladen')">
+            </span>
+            <span class="actions" v-else-if="coursesCount > 0">
+                {{ coursesCount + ' ' + $gettext('Veranstaltungen') }}
+            </span>
+        </caption>
+        <thead>
+            <tr class="sortable">
+                <th v-if="showComplete">
+                    <a
+                        @click.prevent="changeSort('completion')"
+                        class="course-completion"
+                        :title="$gettext('Bearbeitungsstatus')"
+                    >
+                        {{ $gettext('Bearbeitungsstatus') }}
+                    </a>
+                    <studip-icon :shape="sort.direction === 'ASC' ? 'arr_1down' : 'arr_1up'"
+                                 v-if="sort.by === 'completion'"
+                                 class="text-bottom"></studip-icon>
+                </th>
+                <th v-for="activeField in sortedActivatedFields" :key="`field-${activeField}`">
+                    <a href="#"
+                       @click.prevent="changeSort(activeField)"
+                       :title="sort.by === activeField && sort.direction === 'ASC' ? $gettextInterpolate('Sortiert aufsteigend nach %{field}', {field: fields[activeField]}) : (sort.by === activeField && sort.direction === 'DESC' ? $gettextInterpolate('Sortiert absteigend nach %{ field } ', { field: fields[activeField]}) : $gettextInterpolate('Sortieren nach %{ field }', { field: fields[activeField]}))"
+                       v-if="!unsortableFields.includes(activeField)"
+                    >
+                        {{ fields[activeField] }}
+                        <studip-icon :shape="sort.direction === 'ASC' ? 'arr_1down' : 'arr_1up'"
+                                     v-if="sort.by === activeField"
+                                     class="text-bottom"></studip-icon>
+                    </a>
+                    <template v-else>
+                        {{ fields[activeField] }}
+                    </template>
+                </th>
+                <th class="actions" style="text-align: center;">
+                    {{ $gettext('Aktion') }}
+                </th>
+            </tr>
+            <tr v-if="buttons.top">
+                <th v-html="buttons.top" style="text-align: right" :colspan="colspan"></th>
+            </tr>
+        </thead>
+        <tbody :class="{ loading: isLoading }">
+            <tr v-for="course in sortedCourses"
+                :key="course.id"
+                :class="course.id === currentLine ? 'selected' : ''"
+                @click="currentLine = course.id">
+                <td v-if="showComplete">
+                    <button :href="getURL('dispatch.php/admin/courses/toggle_complete/' + course.id)"
+                            class="course-completion undecorated"
+                            :data-course-completion="course.completion"
+                            :title="(course.completion > 0 ? (course.completion == 1 ? $gettext('Veranstaltung in Bearbeitung.') : $gettext('Veranstaltung komplett.')) : $gettext('Veranstaltung neu.')) + ' ' +  $gettext('Klicken zum Ändern des Status.')"
+                            @click.prevent="toggleCompletionState(course.id)">
+                        {{ $gettext('Bearbeitungsstatus ändern') }}
+                    </button>
+                </td>
+                <td v-for="active_field in sortedActivatedFields" :key="active_field">
+                    <div v-html="course[active_field]"></div>
+                    <a v-if="active_field === 'name' && getChildren(course).length > 0"
+                       @click.prevent="toggleOpenChildren(course.id)"
+                       href="">
+                        <studip-icon :shape="open_children.indexOf(course.id) === -1 ? 'add' : 'remove'" class="text-bottom"></studip-icon>
+                        {{ $gettextInterpolate(
+                            $gettext('%{ n } Unterveranstaltungen'),
+                            { n: getChildren(course).length }
+                        ) }}
+                    </a>
+                </td>
+                <td class="actions" v-html="course.action">
+                </td>
+            </tr>
+            <tr v-if="sortedCourses.length === 0 && coursesCount <= 500 && displayLimitedLines && coursesLoaded">
+                <td :colspan="colspan">
+                    {{ $gettext('Keine Ergebnisse') }}
+                </td>
+            </tr>
+            <tr v-if="coursesCount > maxCourses && displayLimitedLines">
+                <td :colspan="colspan">
+                    {{
+                        $gettextInterpolate(
+                            $gettext(`%{ n } Veranstaltungen entsprechen Ihrem Filter. Schränken Sie nach Möglichkeit die Filter weiter ein.`),
+                            { n: coursesCount }
+                        )
+                    }}
+                    <a href="" @click.prevent="loadCourses({withoutLimit: true}); displayLimitedLines = false;">
+                        {{ $gettext('Alle anzeigen') }}
+                    </a>
+                </td>
+            </tr>
+            <tr v-if="!coursesLoaded">
+                <td :colspan="colspan">
+                    {{ $gettext('Daten werden geladen ...') }}
+                </td>
+            </tr>
+        </tbody>
+        <tfoot v-if="buttons.bottom">
+            <tr>
+                <td v-html="buttons.bottom" style="text-align: right" :colspan="colspan"></td>
+            </tr>
+        </tfoot>
+    </table>
+
+</template>
+<script>
+import { mapActions, mapGetters, mapState } from 'vuex';
+
+export default {
+    name: 'AdminCourses',
+    props: {
+        maxCourses: Number,
+        showComplete: {
+            type: Boolean,
+            default: false,
+        },
+        fields: Object,
+        unsortableFields: Array,
+        sortBy: String,
+        sortFlag: String,
+    },
+    data() {
+        return {
+            sort: {
+                by: this.sortBy,
+                direction: this.sortFlag,
+            },
+            currentLine: null,
+            displayLimitedLines: true,
+            open_children: [],
+        };
+    },
+    created() {
+        this.loadCourses();
+    },
+    computed: {
+        ...mapState('admincourses', [
+            'activatedFields',
+            'buttons',
+            'courses',
+            'coursesCount',
+            'coursesLoaded',
+            'filters',
+        ]),
+        ...mapGetters('admincourses', [
+            'isLoading',
+        ]),
+        colspan () {
+            let colspan = this.activatedFields.length + 1;
+            if (this.showComplete) {
+                colspan += 1;
+            }
+            return colspan;
+        },
+        sortedCourses() {
+            let maincourses = this.courses.filter(c => !c.parent_course);
+            maincourses = this.sortArray(maincourses);
+
+            let sorted_courses = [];
+            let children = [];
+            for (let i in maincourses) {
+                sorted_courses.push(maincourses[i]);
+                if (this.open_children.indexOf(maincourses[i].id) !== -1) {
+                    children = this.getChildren(maincourses[i]);
+                    children = this.sortArray(children);
+                    for (let k in children) {
+                        sorted_courses.push(children[k]);
+                    }
+                }
+            }
+            return sorted_courses;
+        },
+        sortedActivatedFields() {
+            return Object.keys(this.fields).filter(f => this.activatedFields.includes(f));
+        },
+        loadingIndicator() {
+            return STUDIP.ASSETS_URL + 'images/loading-indicator.svg';
+        }
+    },
+    methods: {
+        ...mapActions('admincourses', [
+            'changeActionArea',
+            'changeFilter',
+            'loadCourses',
+            'loadCourse',
+            'toggleActiveField',
+            'toggleCompletionState',
+        ]),
+        getChildren(course) {
+            return this.courses.filter(c => c.parent_course === course.id);
+        },
+        toggleOpenChildren(course_id) {
+            if (!this.open_children.includes(course_id)) {
+                this.open_children.push(course_id);
+            } else {
+                this.open_children = this.open_children.filter(cid => cid !== course_id);
+            }
+        },
+        changeSort(column) {
+            if (this.sort.by === column) {
+                this.sort.direction = this.sort.direction === 'ASC' ? 'DESC' : 'ASC';
+            } else {
+                this.currentLine = null;
+                this.sort.direction = 'ASC';
+            }
+            this.sort.by = column;
+
+            $.post(STUDIP.URLHelper.getURL('dispatch.php/admin/courses/sort'), {
+                sortby: column,
+                sortflag: this.sort.direction,
+            });
+        },
+        sortArray (array) {
+            if (!array.length) {
+                return [];
+            }
+            let sortby = this.sort.by;
+            if (!this.activatedFields.includes(sortby) && sortby !== 'completion') {
+                return array;
+            }
+
+            const striptags = function (text) {
+                if (typeof text === "string") {
+                    return text.replace(/(<([^>]+)>)/gi, "");
+                } else {
+                    return text;
+                }
+            };
+
+            // Define sort direction by this factor
+            const directionFactor = this.sort.direction === 'ASC' ? 1 : -1;
+
+            // Default sort function by string comparison of field
+            const collator = new Intl.Collator(String.locale, {numeric: true, sensitivity: 'base'});
+            let sortFunction = function (a, b) {
+                return collator.compare(striptags(a[sortby]), striptags(b[sortby]));
+            };
+
+            let is_numeric = true;
+            for (let i in array) {
+                if (striptags(array[i][sortby]) && isNaN(striptags(array[i][sortby]))) {
+                    is_numeric = false;
+                    break;
+                }
+            }
+            if (is_numeric) {
+                sortFunction = function (a, b) {
+                    return (striptags(a[sortby]) ? parseInt(striptags(a[sortby]), 10) : 0)
+                        - (striptags(b[sortby]) ? parseInt(striptags(b[sortby]), 10) : 0);
+                };
+            }
+
+            // Actual sort on copy of array
+            return array.concat().sort((a, b) => directionFactor * sortFunction(a, b));
+        },
+        getURL(url, params = {}) {
+            return STUDIP.URLHelper.getURL(url, params);
+        },
+    }
+};
+</script>
diff --git a/resources/vue/store/AdminCoursesStore.js b/resources/vue/store/AdminCoursesStore.js
new file mode 100644
index 0000000000000000000000000000000000000000..e3df44ece1914c5dc680c460e90b576bad21757c
--- /dev/null
+++ b/resources/vue/store/AdminCoursesStore.js
@@ -0,0 +1,170 @@
+import Screenreader from '../../assets/javascripts/lib/screenreader.js';
+import { $gettext } from '../../assets/javascripts/lib/gettext.js';
+
+export default {
+    namespaced: true,
+
+    state: () => ({
+        actionArea: 1,
+        activatedFields: [],
+        buttons: {
+            top: '',
+            bottom: '',
+        },
+        courses: [],
+        coursesCount: null,
+        coursesLoaded: false,
+        filters: {},
+        loadingXhr: false,
+    }),
+
+    getters: {
+        getCourseById: (state) => (courseId) => {
+            return state.courses.find((course) => course.id === courseId);
+        },
+        isLoading(state) {
+            return state.loadingXhr !== null;
+        }
+    },
+
+    mutations: {
+        setActionArea(state, area) {
+            state.actionArea = area;
+        },
+        setActivatedFields(state, fields) {
+            state.activatedFields = fields;
+        },
+        setButtons(state, { top, bottom }) {
+            state.buttons.top = top ?? state.buttons.top;
+            state.buttons.bottom = bottom ?? state.buttons.bottom;
+        },
+        setCourse(state, payload) {
+            state.courses = state.courses.filter(c => c.id !== payload.courseId);
+            if (payload.data) {
+                state.courses.push(payload.data);
+            }
+        },
+        setCourses(state, courses, count = null) {
+            state.courses = courses;
+            state.coursesCount = count ?? courses.length;
+        },
+        setCoursesLoaded(state, loaded = true) {
+            state.coursesLoaded = loaded;
+        },
+        setFilter(state, filters) {
+            state.filters = {
+                ...state.filters,
+                ...filters,
+            };
+        },
+    },
+
+    actions: {
+        loadCourse({ commit, state }, courseId) {
+            $.getJSON(STUDIP.URLHelper.getURL('dispatch.php/admin/courses/search'), {
+                ...state.filters,
+                course_id: courseId,
+                action: state.actionArea,
+            }).done((response) =>  {
+                commit('setCourse', {
+                    courseId,
+                    data: response.data[0] ?? null
+                });
+            });
+
+        },
+        loadCourses({ commit, state }, {withoutLimit = false, withoutScreenreaderNotice = false} = {}) {
+            if (state.loadingXhr) {
+                state.loadingXhr.abort();
+            }
+
+            Screenreader.notify('');
+
+            let params = {
+                ...state.filters,
+                action: state.actionArea,
+                activated_fields: state.activatedFields,
+                without_limit: withoutLimit ? 1 : null,
+            };
+
+            // Remove empty items from params
+            params = Object.keys(params)
+                .filter((k) => params[k])
+                .reduce((a, k) => ({ ...a, [k]: params[k] }), {});
+
+            let timeout = null;
+            if (!withoutScreenreaderNotice && !state.coursesLoaded) {
+                timeout = setTimeout(() => {
+                    STUDIP.Screenreader.notify($gettext('Suche läuft.'));
+                }, 800);
+            }
+
+            const xhr = $.ajax({
+                type: 'GET',
+                url: STUDIP.URLHelper.getURL('dispatch.php/admin/courses/search'),
+                dataType: 'json',
+                data: params,
+            });
+            xhr.done((response) => {
+                commit('setCoursesLoaded');
+
+                if (response.data === undefined) {
+                    commit('setCourses', [], response.count);
+                } else {
+                    commit('setCourses', response.data);
+                }
+
+                commit('setButtons', {
+                    top: response.buttons_top ?? null,
+                    bottom: response.buttons_bottom ?? null,
+                });
+            }).always(() => {
+                clearTimeout(timeout);
+                state.loadingXhr = null;
+            });
+
+            state.loadingXhr = xhr;
+        },
+        changeActionArea({ commit, state, dispatch }, area) {
+            if (state.actionArea !== area) {
+                commit('setActionArea', area);
+                dispatch('loadCourses');
+            }
+        },
+        changeFilter({ commit, state, dispatch }, filters) {
+            const changed = Object.entries(filters).some(([key, value]) => {
+                return state.filters[key] === undefined || state.filters[key] !== value;
+            });
+            if (changed) {
+                commit('setFilter', filters);
+                dispatch('loadCourses');
+            }
+        },
+        toggleActiveField({ commit, state, dispatch }, field) {
+            let fields = state.activatedFields;
+            if (fields.includes(field)) {
+                fields = fields.filter(f => f !== field);
+            } else {
+                fields.push(field);
+            }
+
+            commit('setActivatedFields', fields);
+            dispatch('loadCourses');
+        },
+        toggleCompletionState({ commit, getters }, courseId) {
+            $.get(
+                STUDIP.URLHelper.getURL('dispatch.php/admin/courses/toggle_complete/' + courseId)
+            ).done((response) => {
+                const course = getters.getCourseById(courseId);
+                commit('setCourse', {
+                    courseId,
+                    data: {
+                        ...course,
+                        completion: response.state,
+                    }
+                });
+            });
+
+        }
+    }
+}
diff --git a/templates/layouts/base.php b/templates/layouts/base.php
index 2869270fd51ef44883151a09a779ee8ed9abe4c0..67bb2b92f525947995ee2227ad3f992cc3e5f0b3 100644
--- a/templates/layouts/base.php
+++ b/templates/layouts/base.php
@@ -102,6 +102,7 @@ $lang_attr = str_replace('_', '-', $_SESSION['_language']);
 
     <?= $this->render_partial('footer', ['link_params' => $header_template->link_params]); ?>
     <?= SkipLinks::getHTML() ?>
+    <section class="sr-only" id="notes_for_screenreader" aria-live="polite"></section>
 </body>
 </html>
 <?php NotificationCenter::postNotification('PageDidRender', PageLayout::getBodyElementId());
diff --git a/templates/sidebar/search-widget.php b/templates/sidebar/search-widget.php
index 13d6bed0ac6206243c4c8de99f93bdcfbc0384c5..6d870ba246a0e4c0d97bfe2f77df2e88367f8fd9 100644
--- a/templates/sidebar/search-widget.php
+++ b/templates/sidebar/search-widget.php
@@ -1,4 +1,8 @@
-<form action="<?= URLHelper::getLink($url) ?>" method="<?= $method ?>" <? if (isset($id)) printf('id="%s"', htmlReady($id)); ?> class="sidebar-search">
+<form action="<?= URLHelper::getLink($url) ?>"
+      method="<?= $method ?>"
+      <? if (isset($id)) printf('id="%s"', htmlReady($id)); ?>
+      <?= $onsubmit ? 'onsubmit="'.htmlReady($onsubmit).'"' : '' ?>
+      class="sidebar-search">
 <? foreach ($url_params as $key => $value): ?>
     <?=addHiddenFields($key,$value)?>
 <? endforeach; ?>
diff --git a/templates/sidebar/select-widget.php b/templates/sidebar/select-widget.php
index 7c7b039f60ec29e2041cf8cb9ed1eb17f14ac707..b7aa596af208cb3a96699385df0c5132ba0eb336 100644
--- a/templates/sidebar/select-widget.php
+++ b/templates/sidebar/select-widget.php
@@ -1,9 +1,15 @@
-<form action="<?= URLHelper::getLink($url) ?>" method="<?= $method ?>">
+<form action="<?= URLHelper::getLink($url) ?>"
+      <?= $onsubmit ? 'onsubmit="'.htmlReady($onsubmit).'"' : '' ?>
+      method="<?= $method ?>">
     <?= \SelectWidget::arrayToHiddenInput($params) ?>
     <?= (strtolower($method) == 'post') ?  CSRFProtection::tokenTag() : ''; ?>
-    <select class="sidebar-selectlist <?= $class ?> <? if ($__is_nested): ?>nested-select<? endif; ?>" <? !empty($size) ? printf('size="%u"', $size) : '' ?> <?= !empty($attributes) ? arrayToHtmlAttributes($attributes) : '' ?>
-            name="<?= sprintf('%s%s', htmlReady($name), $multiple ? '[]' : '') ?>" <? if ($multiple) echo 'multiple'; ?>
-            aria-label="<?= htmlReady($title) ?>" <?= $dropdownAutoWidth ? 'data-dropdown-auto-width="1"' : '' ?>>
+    <select class="sidebar-selectlist <?= $class ?> <? if ($__is_nested): ?>nested-select<? endif; ?>"
+            <? !empty($size) ? printf('size="%u"', $size) : '' ?>
+            <?= !empty($attributes) ? arrayToHtmlAttributes($attributes) : '' ?>
+            name="<?= sprintf('%s%s', htmlReady($name), $multiple ? '[]' : '') ?>"
+            <? if ($multiple) echo 'multiple'; ?>
+            aria-label="<?= htmlReady($title) ?>"
+            <?= $dropdownAutoWidth ? 'data-dropdown-auto-width="1"' : '' ?>>
 
     <? foreach ($elements as $element): ?>
         <? if ($element instanceof SelectGroupElement && count($element->getElements()) > 0): ?>
diff --git a/templates/sidebar/selector-widget.php b/templates/sidebar/selector-widget.php
index 62f384cb9f0eeae4b72de68eb04089e45a6c7c89..f96e6b3227520d544986cf9072860875b664de24 100644
--- a/templates/sidebar/selector-widget.php
+++ b/templates/sidebar/selector-widget.php
@@ -1,4 +1,6 @@
-<form action="<?= URLHelper::getLink($url, [], true) ?>" method="<?= $method?>" class="selector-widget">
+<form action="<?= URLHelper::getLink($url, [], true) ?>"
+      method="<?= $method?>"
+      class="selector-widget">
     <?= ($method == 'post' ? CSRFProtection::tokenTag() : '') ?>
     <select name="<?=htmlReady($name)?>" class="sidebar-selectlist submit-upon-select text-top" size="<?= (int) $size ?: 8 ?>" aria-label="<?= _("Wählen Sie ein Objekt aus. Sie gelangen dann zur neuen Seite.") ?>">
     <? foreach ($elements as $element): ?>