diff --git a/aleksis/apps/alsijil/assets/components/coursebook/CourseBook.graphql b/aleksis/apps/alsijil/assets/components/coursebook/CourseBook.graphql
deleted file mode 100644
index 9af1bf9dd9ffb33e16f12ebdd969393f40cab7f1..0000000000000000000000000000000000000000
--- a/aleksis/apps/alsijil/assets/components/coursebook/CourseBook.graphql
+++ /dev/null
@@ -1,80 +0,0 @@
-query CourseBook($lessonId: ID!) {
-  excuseTypes {
-    id
-    name
-    shortName
-  }
-  lesson: lessonById(id: $lessonId) {
-    groups {
-      name
-      shortName
-      members {
-        id
-        fullName
-      }
-    }
-    subject {
-      name
-    }
-    plannedLessonperiodsDatetimes {
-      year
-      week
-      datetimeStart
-      lessonPeriod{
-        id
-        period{
-          period
-        }
-      }
-    }
-  }
-  lessonDocumentations: lessonDocumentationsByLessonId(id: $lessonId) {
-    id
-    topic
-    homework
-    groupNote
-    year
-    week
-    lessonPeriod {
-      id
-      period {
-        id
-        period
-      }
-    }
-    event {
-      id
-    }
-    extraLesson {
-      id
-    }
-    period
-    date
-    personalNotes {
-      id
-      person {
-        id
-        fullName
-      }
-      tardiness
-      absent
-      excused
-      excuseType {
-        id
-        name
-        shortName
-      }
-      remarks
-      extraMarks {
-        id
-        name
-        shortName
-      }
-    }
-  }
-  extraMarks {
-    id
-    name
-    shortName
-  }
-}
diff --git a/aleksis/apps/alsijil/assets/components/coursebook/CourseBook.vue b/aleksis/apps/alsijil/assets/components/coursebook/CourseBook.vue
deleted file mode 100644
index 6c29cc64e5635cf89b825a0e2809c9bf018eb047..0000000000000000000000000000000000000000
--- a/aleksis/apps/alsijil/assets/components/coursebook/CourseBook.vue
+++ /dev/null
@@ -1,85 +0,0 @@
-<template>
-  <ApolloQuery
-    :query="require('./CourseBook.graphql')"
-    :variables="{ lessonId: $route.params.lessonId }"
-  >
-    <template v-slot="{ result: { loading, error, data } }">
-        <!-- Error -->
-        <message-box v-if="error" type="error">{{ $t("alsijil.error_occurred") }}</message-box>
-    
-        <!-- Result -->
-        <div v-else-if="data" class="result apollo">
-          <div class="d-flex justify-space-between">
-            <v-btn text color="primary" :href="$root.urls.select_coursebook()">
-              <v-icon left>mdi-chevron-left</v-icon>
-              {{ $t("alsijil.back") }}
-            </v-btn>
-            <update-indicator @manual-update="updateManually()" ref="indicator" :status="status"></update-indicator>
-          </div>
-          <v-row>
-            <v-col cols="12">
-              <lesson-documentations
-                :lesson-documentations="data.lessonDocumentations"
-                :planned-lesson-periods-date-times="data.lesson.plannedLessonperiodsDatetimes"
-                :groups="data.lesson.groups"
-                :excuse-types="data.excuseTypes"
-                :extra-marks="data.extraMarks"
-                :save-lesson-documentations-per-week="saveLessonDocumentationsPerWeek"
-              />
-            </v-col>
-          </v-row>
-        </div>
-        <!-- No result or Loading -->
-        <div v-else class="text-center">
-          <v-progress-circular
-            indeterminate
-            color="primary"
-            class="ma-auto"
-          ></v-progress-circular>
-        </div>
-    </template>
-  </ApolloQuery>
-</template>
-
-<script>
-import {CHANGES, SAVED, UPDATING} from "../../UpdateStatuses.js";
-import UpdateIndicator from "./UpdateIndicator.vue";
-import LessonDocumentations from "./LessonDocumentations.vue";
-
-export default {
-    components: {
-        UpdateIndicator,
-        LessonDocumentations,
-    },
-    props: [ "saveLessonDocumentationsPerWeek" ],
-    methods: {
-        processDataChange(event) {
-            this.status = CHANGES;
-            // alert("Probably save the data");
-            console.log(event);
-            setTimeout(() => {
-                this.status = UPDATING;
-            }, 500)
-
-            setTimeout(() => {
-                this.status = SAVED;
-            }, 1000)
-
-        },
-        updateManually(event) {
-            alert("Data sync triggered manually");
-            this.status = UPDATING;
-            setTimeout(() => {
-                this.status = SAVED;
-            }, 500)
-        },
-    },
-    name: "course-book",
-    data: () => {
-        return {
-            ping: "ping",
-            status: SAVED,
-        }
-    }
-}
-</script>
diff --git a/aleksis/apps/alsijil/assets/index.js b/aleksis/apps/alsijil/assets/index.js
deleted file mode 100644
index 77d41966e5d3688accb0916b457b6be9ea390c73..0000000000000000000000000000000000000000
--- a/aleksis/apps/alsijil/assets/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-export default [
-    { path: "/coursebook/:lessonId", component: () => import("./components/coursebook/CourseBook.vue"), props: true },
-];
diff --git a/aleksis/apps/alsijil/assets/messages/de.json b/aleksis/apps/alsijil/assets/messages/de.json
deleted file mode 100644
index db9d51e142764360a680f4b53e4e913f1d239595..0000000000000000000000000000000000000000
--- a/aleksis/apps/alsijil/assets/messages/de.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
-  "coursebook": {
-    "title": "Kursbuch"
-  }
-}
diff --git a/aleksis/apps/alsijil/assets/UpdateStatuses.js b/aleksis/apps/alsijil/frontend/UpdateStatuses.js
similarity index 100%
rename from aleksis/apps/alsijil/assets/UpdateStatuses.js
rename to aleksis/apps/alsijil/frontend/UpdateStatuses.js
diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/CourseBook.vue b/aleksis/apps/alsijil/frontend/components/coursebook/CourseBook.vue
new file mode 100644
index 0000000000000000000000000000000000000000..3994664e5c80781297d2a054de9c70f0781a53e8
--- /dev/null
+++ b/aleksis/apps/alsijil/frontend/components/coursebook/CourseBook.vue
@@ -0,0 +1,118 @@
+<template>
+  <div>
+    <div class="text-h2">
+      {{ title(courseBookLesson) }}
+    </div>
+    <div v-if="courseBookLesson && alsijilSitePreferences">
+      <div class="d-flex justify-space-between">
+        <v-btn text color="primary" :to="{ name: 'alsijil.courseBookList' }">
+          <v-icon left>mdi-chevron-left</v-icon>
+          {{ $t("alsijil.back") }}
+        </v-btn>
+        <update-indicator @manual-update="updateManually()" ref="indicator" :status="status"></update-indicator>
+      </div>
+      <v-row>
+        <v-col cols="12">
+          <lesson-documentations
+            :lesson-documentations="courseBookLessonDocumentations"
+            :planned-lesson-periods-date-times="courseBookLesson.plannedLessonperiodsDatetimes"
+            :groups="courseBookLesson.groups"
+            :excuse-types="excuseTypes"
+            :extra-marks="extraMarks"
+            :save-lesson-documentations-per-week="alsijilSitePreferences.saveLessonDocumentationsByWeek"
+          />
+        </v-col>
+      </v-row>
+    </div>
+  </div>
+</template>
+
+<script>
+import {CHANGES, SAVED, UPDATING} from "../../UpdateStatuses.js";
+import UpdateIndicator from "./UpdateIndicator.vue";
+import LessonDocumentations from "./LessonDocumentations.vue";
+
+import gqlCourseBookLesson from "./courseBookLesson.graphql";
+import gqlCourseBookLessonDocumentations from "./courseBookLesson.graphql";
+import gqlExcuseTypes from "./excuseTypes.graphql";
+import gqlExtraMarks from "./extraMarks.graphql";
+import gqlAlsijilSitePreferences from "./alsijilSitePreferences.graphql";
+
+export default {
+    components: {
+        UpdateIndicator,
+        LessonDocumentations,
+    },
+    props: {
+        lessonId: String,
+    },
+    apollo: {
+        courseBookLesson: {
+            query: gqlCourseBookLesson,
+            variables() {
+                return {
+                    lessonId: this.lessonId,
+                };
+            },
+            update: (data) => data.lesson,
+        },
+        courseBookLessonDocumentations: {
+            query: gqlCourseBookLessonDocumentations,
+            variables() {
+                return {
+                    lessonId: this.lessonId,
+                };
+            },
+            update: (data) => data.lessonDocumentationsByLessonId,
+        },
+        excuseTypes: gqlExcuseTypes,
+        extraMarks: gqlExtraMarks,
+        alsijilSitePreferences: gqlAlsijilSitePreferences,
+    },
+    methods: {
+        processDataChange(event) {
+            this.status = CHANGES;
+            // alert("Probably save the data");
+            console.log(event);
+            setTimeout(() => {
+                this.status = UPDATING;
+            }, 500)
+
+            setTimeout(() => {
+                this.status = SAVED;
+            }, 1000)
+
+        },
+        updateManually(event) {
+            alert("Data sync triggered manually");
+            this.status = UPDATING;
+            setTimeout(() => {
+                this.status = SAVED;
+            }, 500)
+        },
+        title(lesson) {
+            return this.$t('alsijil.coursebook.menu_title') + (lesson && lesson.groups && lesson.subject ? (" " + lesson.groups.map(g => g.shortName).join() + " · " + lesson.subject.name) : "");
+        },
+    },
+    watch: {
+        courseBookLesson: {
+            handler(newLesson) {
+                this.$root.$setPageTitle(this.title(newLesson));
+            },
+            deep: true,
+        }
+    },
+    name: "course-book",
+    data: () => {
+        return{
+            ping: "ping",
+            status: SAVED,
+            alsijilSitePreferences: null,
+            courseBookLesson: null,
+            courseBookLessonDocumentations: null,
+            excuseTypes: null,
+            extraMarks: null,
+        }
+    }
+}
+</script>
diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/CourseBookList.vue b/aleksis/apps/alsijil/frontend/components/coursebook/CourseBookList.vue
new file mode 100644
index 0000000000000000000000000000000000000000..1cca0dd2aa28bd1a6b7e80a7dc2c3a4c38991e24
--- /dev/null
+++ b/aleksis/apps/alsijil/frontend/components/coursebook/CourseBookList.vue
@@ -0,0 +1,84 @@
+<template>
+  <v-row>
+    <v-col cols="12" xl="12" class="d-flex">
+      <div class="text-h2">
+        {{ $t("alsijil.coursebook.title") }}
+      </div>
+    </v-col>
+    <v-col
+      v-if="$apollo.queries.coursebookLessons.loading"
+      cols="12"
+      xs="12"
+      sm="6"
+      md="6"
+      lg="4"
+      xl="3"
+      class="d-flex"
+    >
+      <v-card
+        class="flex-grow-1"
+        v-for="idx in 4"
+        :key="idx"
+      >
+        <v-skeleton-loader
+          type="heading, text@2, actions"
+        ></v-skeleton-loader>
+      </v-card>
+    </v-col>
+    <v-col v-else-if="coursebookLessons" v-for="lesson in coursebookLessons" xs="12" sm="6" md="6" lg="4" xl="3" class="d-flex">
+      <v-card class="flex-grow-1">
+        <v-card-title>
+          {{ lesson.groups.map(g => g.shortName).join() + " · " + lesson.subject.name }}
+        </v-card-title>
+        <v-card-subtitle>
+          {{ lesson.validity.dateStart + " - " + lesson.validity.dateEnd }}
+        </v-card-subtitle>
+        <v-card-text>
+          {{ lesson.teachers.map(t => t.fullName).join() }}
+        </v-card-text>
+        <v-card-actions>
+          <v-btn :to="{ name: 'alsijil.courseBookByID', params: { lessonId: lesson.id } }" text color="secondary">
+            <v-icon left>mdi-book-search-outline</v-icon>
+            {{ $t("alsijil.coursebook.open_coursebook") }}
+          </v-btn>
+        </v-card-actions>
+      </v-card>
+    </v-col>
+    <v-container
+      class="text-center fill-height"
+      style="height: calc(100vh - 58px);"
+      v-else-if="!coursebookLessons"
+    >
+      <v-row align="center">
+        <v-col>
+          <h1 class="text-h3 primary--text">
+            <v-icon color="error" x-large>mdi-book-off-outline</v-icon>
+            {{ $t("alsijil.coursebook.no_coursebook") }}
+          </h1>
+
+          <p> {{ $t("alsijil.coursebook.no_courses_as_teacher") }} </p>
+        </v-col>
+      </v-row>
+    </v-container>
+  </v-row>
+</template>
+
+<script>
+import gqlCourseBookLessons from "./courseBookLessons.graphql";
+
+export default {
+  name: "CourseBookList",
+  data() {
+    return {
+      coursebookLessons: null,
+    }
+  },
+  apollo: {
+    coursebookLessons: gqlCourseBookLessons,
+  },
+}
+</script>
+
+<style scoped>
+
+</style>
\ No newline at end of file
diff --git a/aleksis/apps/alsijil/assets/components/coursebook/LessonDocumentation.vue b/aleksis/apps/alsijil/frontend/components/coursebook/LessonDocumentation.vue
similarity index 98%
rename from aleksis/apps/alsijil/assets/components/coursebook/LessonDocumentation.vue
rename to aleksis/apps/alsijil/frontend/components/coursebook/LessonDocumentation.vue
index e2679b4ade3b67b44b24ef60f96f2d024d29aeab..244938ff45d31a595a65f9f9f4b8fb4f3d165bc6 100644
--- a/aleksis/apps/alsijil/assets/components/coursebook/LessonDocumentation.vue
+++ b/aleksis/apps/alsijil/frontend/components/coursebook/LessonDocumentation.vue
@@ -1,6 +1,6 @@
 <template>
   <ApolloMutation
-      :mutation="require('./LessonDocumentation.graphql')"
+      :mutation="require('./lessonDocumentation.graphql')"
       :variables=lessonDocumentationEdit
       @done="onDone"
   >
diff --git a/aleksis/apps/alsijil/assets/components/coursebook/LessonDocumentations.vue b/aleksis/apps/alsijil/frontend/components/coursebook/LessonDocumentations.vue
similarity index 100%
rename from aleksis/apps/alsijil/assets/components/coursebook/LessonDocumentations.vue
rename to aleksis/apps/alsijil/frontend/components/coursebook/LessonDocumentations.vue
diff --git a/aleksis/apps/alsijil/assets/components/coursebook/PersonalNotes.vue b/aleksis/apps/alsijil/frontend/components/coursebook/PersonalNotes.vue
similarity index 100%
rename from aleksis/apps/alsijil/assets/components/coursebook/PersonalNotes.vue
rename to aleksis/apps/alsijil/frontend/components/coursebook/PersonalNotes.vue
diff --git a/aleksis/apps/alsijil/assets/components/coursebook/UpdateIndicator.vue b/aleksis/apps/alsijil/frontend/components/coursebook/UpdateIndicator.vue
similarity index 100%
rename from aleksis/apps/alsijil/assets/components/coursebook/UpdateIndicator.vue
rename to aleksis/apps/alsijil/frontend/components/coursebook/UpdateIndicator.vue
diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/alsijilSitePreferences.graphql b/aleksis/apps/alsijil/frontend/components/coursebook/alsijilSitePreferences.graphql
new file mode 100644
index 0000000000000000000000000000000000000000..527fb25a62ac80afb4589234178cf63fbcb53023
--- /dev/null
+++ b/aleksis/apps/alsijil/frontend/components/coursebook/alsijilSitePreferences.graphql
@@ -0,0 +1,5 @@
+{
+  alsijilSitePreferences {
+    saveLessonDocumentationsByWeek
+  }
+}
\ No newline at end of file
diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/courseBookLesson.graphql b/aleksis/apps/alsijil/frontend/components/coursebook/courseBookLesson.graphql
new file mode 100644
index 0000000000000000000000000000000000000000..cca66a4b6874f5b1b4b1ce283bdbc366e0d037e1
--- /dev/null
+++ b/aleksis/apps/alsijil/frontend/components/coursebook/courseBookLesson.graphql
@@ -0,0 +1,26 @@
+query courseBookLesson($lessonId: ID!) {
+  lesson: lessonById(id: $lessonId) {
+    groups {
+      name
+      shortName
+      members {
+        id
+        fullName
+      }
+    }
+    subject {
+      name
+    }
+    plannedLessonperiodsDatetimes {
+      year
+      week
+      datetimeStart
+      lessonPeriod{
+        id
+        period{
+          period
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/courseBookLessonDocumentations.graphql b/aleksis/apps/alsijil/frontend/components/coursebook/courseBookLessonDocumentations.graphql
new file mode 100644
index 0000000000000000000000000000000000000000..1ba37ce5d4a3c1b9a5d3a0264eac0452178321b0
--- /dev/null
+++ b/aleksis/apps/alsijil/frontend/components/coursebook/courseBookLessonDocumentations.graphql
@@ -0,0 +1,47 @@
+query courseBookLessonDocumentations ($lessonId: ID!) {
+  lessonDocumentationsByLessonId(id: $lessonId) {
+    id
+    topic
+    homework
+    groupNote
+    year
+    week
+    lessonPeriod {
+      id
+      period {
+        id
+        period
+      }
+    }
+    event {
+      id
+    }
+    extraLesson {
+      id
+    }
+    period
+    date
+    personalNotes {
+      id
+      person {
+        id
+        fullName
+      }
+      tardiness
+      absent
+      excused
+      excuseType {
+        id
+        name
+        shortName
+      }
+      remarks
+      extraMarks {
+        id
+        name
+          shortName
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/courseBookLessons.graphql b/aleksis/apps/alsijil/frontend/components/coursebook/courseBookLessons.graphql
new file mode 100644
index 0000000000000000000000000000000000000000..6531a76c2a4f5790e027c7f151d21e3540f3c581
--- /dev/null
+++ b/aleksis/apps/alsijil/frontend/components/coursebook/courseBookLessons.graphql
@@ -0,0 +1,18 @@
+{
+  coursebookLessons {
+    groups {
+      shortName
+    }
+    subject {
+      name
+    }
+    validity {
+      dateStart
+      dateEnd
+    }
+    teachers {
+      fullName
+    }
+    id
+  }
+}
diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/excuseTypes.graphql b/aleksis/apps/alsijil/frontend/components/coursebook/excuseTypes.graphql
new file mode 100644
index 0000000000000000000000000000000000000000..6eb0650dcd18e80839ad6e9e8168b0997f6b2707
--- /dev/null
+++ b/aleksis/apps/alsijil/frontend/components/coursebook/excuseTypes.graphql
@@ -0,0 +1,7 @@
+{
+  excuseTypes {
+    id
+    name
+    shortName
+  }
+}
\ No newline at end of file
diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/extraMarks.graphql b/aleksis/apps/alsijil/frontend/components/coursebook/extraMarks.graphql
new file mode 100644
index 0000000000000000000000000000000000000000..ffbb48f2020c7c7fe646f91a8ca612850d6fa204
--- /dev/null
+++ b/aleksis/apps/alsijil/frontend/components/coursebook/extraMarks.graphql
@@ -0,0 +1,7 @@
+{
+  extraMarks {
+    id
+    name
+    shortName
+  }
+}
\ No newline at end of file
diff --git a/aleksis/apps/alsijil/assets/components/coursebook/LessonDocumentation.graphql b/aleksis/apps/alsijil/frontend/components/coursebook/lessonDocumentation.graphql
similarity index 100%
rename from aleksis/apps/alsijil/assets/components/coursebook/LessonDocumentation.graphql
rename to aleksis/apps/alsijil/frontend/components/coursebook/lessonDocumentation.graphql
diff --git a/aleksis/apps/alsijil/frontend/index.js b/aleksis/apps/alsijil/frontend/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..1ac48feef5e5ed3a86bf8d448a3bb70e3ec10a81
--- /dev/null
+++ b/aleksis/apps/alsijil/frontend/index.js
@@ -0,0 +1,288 @@
+export default
+  {
+    meta: {
+      inMenu: true,
+      titleKey: "alsijil.menu_title",
+      icon: "mdi-account-group-outline",
+    },
+    children: [
+      {
+        path: "coursebook/:lessonId(\\d+)/",
+        component: () => import("./components/coursebook/CourseBook.vue"),
+        props: true,
+        name: "alsijil.courseBookByID",
+      },
+      {
+        path: "coursebook/",
+        component: () => import("./components/coursebook/CourseBookList.vue"),
+        name: "alsijil.courseBookList",
+        meta: {
+          inMenu: true,
+          titleKey: "alsijil.coursebook.menu_title",
+          icon: "mdi-book-education-outline",
+          permission: "alsijil.view_coursebook_rule",
+        },
+      },
+      {
+        path: "lesson",
+        component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
+        name: "alsijil.lessonPeriod",
+        meta: {
+          inMenu: true,
+          titleKey: "alsijil.lesson.menu_title",
+          icon: "mdi-alarm",
+          permission: "alsijil.view_lesson_menu_rule",
+        },
+      },
+      {
+        path: "lesson/:year(\\d+)/:week(\\d+)/:id_(\\d+)",
+        component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
+        name: "alsijil.lessonPeriodByCWAndID",
+      },
+      {
+        path: "extra_lesson/:id_(\\d+)/",
+        component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
+        name: "alsijil.extraLessonByID",
+      },
+      {
+        path: "event/:id_(\\d+)/",
+        component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
+        name: "alsijil.eventByID",
+      },
+      {
+        path: "week/",
+        component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
+        name: "alsijil.weekView",
+        meta: {
+          inMenu: true,
+          titleKey: "alsijil.week.menu_title",
+          icon: "mdi-view-week-outline",
+          permission: "alsijil.view_week_menu_rule",
+        },
+      },
+      {
+        path: "week/:year(\\d+)/:week(\\d+)/",
+        component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
+        name: "alsijil.weekViewByWeek",
+      },
+      {
+        path: "week/year/cw/",
+        component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
+        name: "alsijil.weekViewPlaceholders",
+      },
+      {
+        path: "week/:type_/:id_(\\d+)/",
+        component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
+        name: "alsijil.weekViewByTypeAndID",
+      },
+      {
+        path: "week/year/cw/:type_/:id_(\\d+)/",
+        component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
+        name: "alsijil.weekViewPlaceholdersByTypeAndID",
+      },
+      {
+        path: "week/:year(\\d+)/:week(\\d+)/:type_/:id_(\\d+)/",
+        component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
+        name: "alsijil.weekViewByWeekTypeAndID",
+      },
+      {
+        path: "print/group/:id_(\\d+)",
+        component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
+        name: "alsijil.fullRegisterGroup",
+      },
+      {
+        path: "groups/",
+        component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
+        name: "alsijil.myGroups",
+        meta: {
+          inMenu: true,
+          titleKey: "alsijil.groups.menu_title",
+          icon: "mdi-account-multiple-outline",
+          permission: "alsijil.view_my_groups_rule",
+        },
+      },
+      {
+        path: "groups/:pk(\\d+)/",
+        component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
+        name: "alsijil.studentsList",
+      },
+      {
+        path: "persons/",
+        component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
+        name: "alsijil.myStudents",
+        meta: {
+          inMenu: true,
+          titleKey: "alsijil.persons.menu_title",
+          icon: "mdi-account-school-outline",
+          permission: "alsijil.view_my_students_rule",
+        },
+      },
+      {
+        path: "persons/:id_(\\d+)/",
+        component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
+        name: "alsijil.overviewPerson",
+      },
+      {
+        path: "me/",
+        component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
+        name: "alsijil.overviewMe",
+        meta: {
+          inMenu: true,
+          titleKey: "alsijil.my_overview.menu_title",
+          icon: "mdi-chart-box-outline",
+          permission: "alsijil.view_person_overview_menu_rule",
+        },
+      },
+      {
+        path: "notes/:pk(\\d+)/delete/",
+        component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
+        name: "alsijil.deletePersonalNote",
+      },
+      {
+        path: "absence/new/:id_(\\d+)/",
+        component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
+        name: "alsijil.registerAbsenceWithID",
+      },
+      {
+        path: "absence/new/",
+        component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
+        name: "alsijil.registerAbsence",
+        meta: {
+          inMenu: true,
+          titleKey: "alsijil.absence.menu_title",
+          icon: "mdi-message-draw",
+          permission: "alsijil.view_register_absence_rule",
+        },
+      },
+      {
+        path: "extra_marks/",
+        component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
+        name: "alsijil.extraMarks",
+        meta: {
+          inMenu: true,
+          titleKey: "alsijil.extra_marks.menu_title",
+          icon: "mdi-label-variant-outline",
+          permission: "alsijil.view_extramarks_rule",
+        },
+      },
+      {
+        path: "extra_marks/create/",
+        component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
+        name: "alsijil.createExtraMark",
+      },
+      {
+        path: "extra_marks/:pk(\\d+)/edit/",
+        component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
+        name: "alsijil.editExtraMark",
+      },
+      {
+        path: "extra_marks/:pk(\\d+)/delete/",
+        component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
+        name: "alsijil.deleteExtraMark",
+      },
+      {
+        path: "excuse_types/",
+        component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
+        name: "alsijil.excuseTypes",
+        meta: {
+          inMenu: true,
+          titleKey: "alsijil.excuse_types.menu_title",
+          icon: "mdi-label-outline",
+          permission: "alsijil.view_excusetypes_rule",
+        },
+      },
+      {
+        path: "excuse_types/create/",
+        component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
+        name: "alsijil.createExcuseType",
+      },
+      {
+        path: "excuse_types/:pk(\\d+)/edit/",
+        component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
+        name: "alsijil.editExcuseType",
+      },
+      {
+        path: "excuse_types/:pk(\\d+)/delete/",
+        component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
+        name: "alsijil.deleteExcuseType",
+      },
+      {
+        path: "group_roles/",
+        component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
+        name: "alsijil.groupRoles",
+        meta: {
+          inMenu: true,
+          titleKey: "alsijil.group_roles.menu_title_manage",
+          icon: "mdi-clipboard-plus-outline",
+          permission: "alsijil.view_grouproles_rule",
+        },
+      },
+      {
+        path: "group_roles/create/",
+        component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
+        name: "alsijil.createGroupRole",
+      },
+      {
+        path: "group_roles/:pk(\\d+)/edit/",
+        component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
+        name: "alsijil.editGroupRole",
+      },
+      {
+        path: "group_roles/:pk(\\d+)/delete/",
+        component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
+        name: "alsijil.deleteGroupRole",
+      },
+      {
+        path: "groups/:pk(\\d+)/group_roles/",
+        component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
+        name: "alsijil.assignedGroupRoles",
+      },
+      {
+        path: "groups/:pk(\\d+)/group_roles/assign/",
+        component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
+        name: "alsijil.assignGroupRole",
+      },
+      {
+        path: "groups/:pk(\\d+)/group_roles/:role_pk(\\d+)/assign/",
+        component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
+        name: "alsijil.assignGroupRoleByRolePK",
+      },
+      {
+        path: "group_roles/assignments/:pk(\\d+)/edit/",
+        component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
+        name: "alsijil.editGroupRoleAssignment",
+      },
+      {
+        path: "group_roles/assignments/:pk(\\d+)/stop/",
+        component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
+        name: "alsijil.stopGroupRoleAssignment",
+      },
+      {
+        path: "group_roles/assignments/:pk(\\d+)/delete/",
+        component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
+        name: "alsijil.deleteGroupRoleAssignment",
+      },
+      {
+        path: "group_roles/assignments/assign/",
+        component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
+        name: "alsijil.assignGroupRoleMultiple",
+        meta: {
+          inMenu: true,
+          titleKey: "alsijil.group_roles.menu_title_assign",
+          icon: "mdi-clipboard-account-outline",
+          permission: "alsijil.assign_grouprole_for_multiple_rule",
+        },
+      },
+      {
+        path: "all/",
+        component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
+        name: "alsijil.allRegisterObjects",
+        meta: {
+          inMenu: true,
+          titleKey: "alsijil.all_lessons.menu_title",
+          icon: "mdi-format-list-text",
+          permission: "alsijil.view_register_objects_list_rule",
+        },
+      },
+    ],
+  };
diff --git a/aleksis/apps/alsijil/frontend/messages/de.json b/aleksis/apps/alsijil/frontend/messages/de.json
new file mode 100644
index 0000000000000000000000000000000000000000..ea3e2c3287309d22a4919fea006ae95da040a73c
--- /dev/null
+++ b/aleksis/apps/alsijil/frontend/messages/de.json
@@ -0,0 +1,9 @@
+{
+  "alsijil": {
+    "coursebook": {
+      "menu_title": "Kursbuch",
+      "title": "Kursbücher"
+    },
+    "menu_title": "Klassenbuch"
+  }
+}
diff --git a/aleksis/apps/alsijil/assets/messages/en.json b/aleksis/apps/alsijil/frontend/messages/en.json
similarity index 57%
rename from aleksis/apps/alsijil/assets/messages/en.json
rename to aleksis/apps/alsijil/frontend/messages/en.json
index 8982646859803afd3ec7cfafa7e335767f8d9b5e..0466559b9fb01a99276b4135da1b790253f422e8 100644
--- a/aleksis/apps/alsijil/assets/messages/en.json
+++ b/aleksis/apps/alsijil/frontend/messages/en.json
@@ -1,7 +1,8 @@
 {
   "alsijil": {
     "coursebook": {
-      "title": "Coursebook",
+      "menu_title": "Coursebook",
+      "title": "Coursebooks",
       "create_documentation": "Create documentation",
       "choose_week": "Choose week",
       "choose_lesson_date": "Choose lesson date",
@@ -10,7 +11,41 @@
         "updating": "Changes are being synced.",
         "changes": "You have unsaved changes. Click to save them immediately.",
         "error": "There has been an error while saving the latest changes."
-      }
+      },
+      "no_coursebook": "No coursebook",
+      "no_courses_as_teacher": "There are no courses where you are a teacher.",
+      "open_coursebook": "Open in coursebook"
+    },
+    "lesson": {
+      "menu_title": "Current lesson"
+    },
+    "week": {
+      "menu_title": "Current week"
+    },
+    "groups": {
+      "menu_title": "My groups"
+    },
+    "persons": {
+      "menu_title": "My students"
+    },
+    "absence": {
+      "menu_title": "Register absence"
+    },
+    "my_overview": {
+      "menu_title": "My overview"
+    },
+    "extra_marks": {
+      "menu_title": "Extra marks"
+    },
+    "excuse_types": {
+      "menu_title": "Excuse types"
+    },
+    "group_roles": {
+      "menu_title_manage": "Manage group roles",
+      "menu_title_assign": "Assign group roles"
+    },
+    "all_lessons": {
+      "menu_title": "All lessons"
     },
     "period": "Period",
     "period_number": "{number}. period",
@@ -41,6 +76,7 @@
     "error_updating": "Error updating data",
     "cancel": "Cancel",
     "save": "Save",
-    "back": "Back"
+    "back": "Back",
+    "menu_title": "Class register"
   }
 }
diff --git a/aleksis/apps/alsijil/schema.py b/aleksis/apps/alsijil/schema.py
index d7f746170a13985dd7f5b012f71017dfb50c303e..0c30a501897bcf563b5251db85c395eb9cd2c139 100644
--- a/aleksis/apps/alsijil/schema.py
+++ b/aleksis/apps/alsijil/schema.py
@@ -1,11 +1,16 @@
 from datetime import datetime
 
+from django.db.models import Q
+
 import graphene
+from calendarweek import CalendarWeek
 from graphene_django import DjangoObjectType
 
 from aleksis.apps.chronos.models import Lesson
+from aleksis.apps.chronos.schema import LessonType
+from aleksis.apps.chronos.util.date import get_current_year
 from aleksis.core.models import Group, Person
-from aleksis.core.util.core_helpers import get_site_preferences
+from aleksis.core.util.core_helpers import get_site_preferences, has_person
 
 from .models import (
     Event,
@@ -59,6 +64,13 @@ class ExtraMarkType(DjangoObjectType):
         model = ExtraMark
 
 
+class AlsijilSitePreferencesType(graphene.ObjectType):
+  save_lesson_documentations_by_week = graphene.Boolean()
+
+  def resolve_save_lesson_documentations_by_week(root, info, **kwargs):
+      return root["alsijil__save_lesson_documentations_by_week"]
+
+
 class LessonDocumentationMutation(graphene.Mutation):
     class Arguments:
         year = graphene.Int(required=True)
@@ -78,18 +90,18 @@ class LessonDocumentationMutation(graphene.Mutation):
 
     @classmethod
     def mutate(
-        cls,
-        root,
-        info,
-        year,
-        week,
-        lesson_period_id=None,
-        event_id=None,
-        extra_lesson_id=None,
-        lesson_documentation_id=None,
-        topic=None,
-        homework=None,
-        group_note=None,
+            cls,
+            root,
+            info,
+            year,
+            week,
+            lesson_period_id=None,
+            event_id=None,
+            extra_lesson_id=None,
+            lesson_documentation_id=None,
+            topic=None,
+            homework=None,
+            group_note=None,
     ):
 
         lesson_period = LessonPeriod.objects.filter(pk=lesson_period_id).first()
@@ -114,13 +126,13 @@ class LessonDocumentationMutation(graphene.Mutation):
         lesson_documentation.save()
 
         if (
-            get_site_preferences()["alsijil__save_lesson_documentations_by_week"]
-            and (
+                get_site_preferences()["alsijil__save_lesson_documentations_by_week"]
+                and (
                 lesson_documentation.topic
                 or lesson_documentation.homework
                 or lesson_documentation.group_note
-            )
-            and lesson_documentation.lesson_period
+        )
+                and lesson_documentation.lesson_period
         ):
             lesson_documentation.carry_over_data(
                 LessonPeriod.objects.filter(lesson=lesson_documentation.lesson_period.lesson), True
@@ -147,18 +159,18 @@ class PersonalNoteMutation(graphene.Mutation):
 
     @classmethod
     def mutate(
-        cls,
-        root,
-        info,
-        person_id,
-        lesson_documentation,
-        personal_note_id=None,
-        late=None,
-        absent=None,
-        excused=None,
-        excuse_type=None,
-        remarks=None,
-        extra_marks=None,
+            cls,
+            root,
+            info,
+            person_id,
+            lesson_documentation,
+            personal_note_id=None,
+            late=None,
+            absent=None,
+            excused=None,
+            excuse_type=None,
+            remarks=None,
+            extra_marks=None,
     ):
         person = Person.objects.get(pk=person_id)
         lesson_documentation = LessonDocumentation.objects.get(pk=lesson_documentation)
@@ -205,8 +217,10 @@ class Query(graphene.ObjectType):
     lesson_documentations = graphene.List(LessonDocumentationType)
     lesson_documentation_by_id = graphene.Field(LessonDocumentationType, id=graphene.ID())
     lesson_documentations_by_lesson_id = graphene.List(LessonDocumentationType, id=graphene.ID())
+    coursebook_lessons = graphene.List(LessonType)
     personal_notes = graphene.List(PersonalNoteType)
     extra_marks = graphene.List(ExtraMarkType)
+    alsijil_site_preferences = graphene.Field(AlsijilSitePreferencesType)
 
     def resolve_excuse_types(root, info, **kwargs):
         # FIXME do permission stuff
@@ -241,9 +255,40 @@ class Query(graphene.ObjectType):
             "-year", "-week", "-lesson_period__period__weekday", "-lesson_period__period__period"
         )
 
-    def resolve_personal_notes(root, info, **kwargs):
-        # FIXME do permission stuff
-        return PersonalNote.objects.all()
+    def resolve_coursebook_lessons(root, info, **kwargs):  # noqa
+        current_week = CalendarWeek.current_week()
+        current_year = get_current_year()
+        # Show all future and the ones of last week
+
+        last_week, last_week_year = (
+            (current_week - 1, current_year)
+            if current_week >= 2
+            else (CalendarWeek.get_last_week_of_year(current_year - 1).week, current_year - 1)
+        )
+
+        last_week_query = Q(
+            lesson_periods__substitutions__week=last_week,
+            lesson_periods__substitutions__year=last_week_year,
+        )
+        this_week_query = Q(
+            lesson_periods__substitutions__week__gte=current_week,
+            lesson_periods__substitutions__year=current_year,
+        )
+        next_years_query = Q(lesson_periods__substitutions__year__gt=current_year)
+        return Lesson.objects.filter(
+            Q(teachers=info.context.user.person) | (Q(lesson_periods__substitutions__teachers=info.context.user.person)
+                                                    & (last_week_query | this_week_query | next_years_query)
+                                                    )
+        ).for_current_or_all().distinct() if has_person(info.context.user) else None
+
+    def resolve_alsijil_site_preferences(root, info, **kwargs):
+        return info.context.site.preferences
+
+
+def resolve_personal_notes(root, info, **kwargs):
+    # FIXME do permission stuff
+    return PersonalNote.objects.all()
+
 
-    def resolve_extra_marks(root, info, **kwargs):
-        return ExtraMark.objects.all()
+def resolve_extra_marks(root, info, **kwargs):
+    return ExtraMark.objects.all()
diff --git a/aleksis/apps/alsijil/templates/alsijil/class_register/coursebook.html b/aleksis/apps/alsijil/templates/alsijil/class_register/coursebook.html
deleted file mode 100644
index 80378c66a06cbac1951cbf38f1d6d49bea896b49..0000000000000000000000000000000000000000
--- a/aleksis/apps/alsijil/templates/alsijil/class_register/coursebook.html
+++ /dev/null
@@ -1,16 +0,0 @@
-{% extends "core/vue_base.html" %}
-{% load static i18n %}
-{% load render_bundle from webpack_loader %}
-
-{% block page_title %}
-  {% trans "Coursebook" %}
-{% endblock %}
-{% block browser_title %}{% trans "Coursebook" %} {{ lesson }}{% endblock %}
-{% block content %}
-<div class="text-h5">{{ lesson }}</div>
-<router-view save-lesson-documentations-per-week={{ SITE_PREFERENCES.alsijil__save_lesson_documentations_by_week }} />
-{% endblock %}
-
-{% block extra_body %}
-  {% render_bundle "aleksis.apps.alsijil" %}
-{% endblock %}
diff --git a/aleksis/apps/alsijil/templates/alsijil/class_register/select_coursebook.html b/aleksis/apps/alsijil/templates/alsijil/class_register/select_coursebook.html
deleted file mode 100644
index 57114df23ebd2e1631f56596fd89c220740fdad8..0000000000000000000000000000000000000000
--- a/aleksis/apps/alsijil/templates/alsijil/class_register/select_coursebook.html
+++ /dev/null
@@ -1,53 +0,0 @@
-{% extends "core/vue_base.html" %}
-{% load static i18n %}
-{% load render_bundle from webpack_loader %}
-
-{% block page_title %}{% trans "Select Coursebook" %}{% endblock %}
-{% block browser_title %}{% trans "Select Coursebook" %}{% endblock %}
-{% block content %}
-  <v-row>
-    {% for lesson in lessons %}
-      <v-col xs="12" sm="6" md="6" lg="4" xl="3" class="d-flex">
-        <v-card class="flex-grow-1">
-          <v-card-title>
-            {% for group in lesson.groups.all %}{{ group.short_name }}{% if not forloop.last %},{% endif %}{% endfor %}
-            · {{ lesson.subject.name }}
-          </v-card-title>
-          <v-card-subtitle>
-            {{ lesson.validity.date_start }}-{{ lesson.validity.date_end }}
-          </v-card-subtitle>
-          <v-card-text>
-            {{ lesson.teachers.all|join:"," }}
-          </v-card-text>
-{#          <v-spacer></v-spacer>#}
-          <v-card-actions>
-            <v-btn :href="urls.coursebook({{ lesson.pk }})" text color="secondary">
-              <v-icon left>mdi-book-search-outline</v-icon>
-              {% trans "Open in coursebook" %}
-            </v-btn>
-          </v-card-actions>
-        </v-card>
-      </v-col>
-    {% empty %}
-      <v-container
-        class="text-center fill-height"
-        style="height: calc(100vh - 58px);"
-      >
-        <v-row align="center">
-          <v-col>
-            <h1 class="text-h3 primary--text">
-              <v-icon color="error" x-large>mdi-book-off-outline</v-icon>
-              {% trans "No Coursebook" %}
-            </h1>
-
-            <p>{% trans "There are no courses where you are a teacher." %} </p>
-          </v-col>
-        </v-row>
-      </v-container>
-    {% endfor %}
-  </v-row>
-{% endblock %}
-
-{% block extra_body %}
-  {% render_bundle "aleksis.apps.alsijil" %}
-{% endblock %}
diff --git a/aleksis/apps/alsijil/urls.py b/aleksis/apps/alsijil/urls.py
index 2cbc63fbbbe9932e3f7fb6e312980971a1fc20dc..b06aebd352774727bb6bddca9dec8a0e28846a69 100644
--- a/aleksis/apps/alsijil/urls.py
+++ b/aleksis/apps/alsijil/urls.py
@@ -3,8 +3,6 @@ from django.urls import path
 from . import views
 
 urlpatterns = [
-    path("coursebook/", views.SelectCoursebookView.as_view(), name="select_coursebook"),
-    path("coursebook/<int:pk>/", views.CoursebookView.as_view(), name="coursebook"),
     path("lesson", views.register_object, {"model": "lesson"}, name="lesson_period"),
     path(
         "lesson/<int:year>/<int:week>/<int:id_>",
diff --git a/aleksis/apps/alsijil/views.py b/aleksis/apps/alsijil/views.py
index 55fc21792f9500fc7d9de0c92dc0d38244a4337f..095bc80d221d31dbc81d2b5af081e430f20432d1 100644
--- a/aleksis/apps/alsijil/views.py
+++ b/aleksis/apps/alsijil/views.py
@@ -1362,16 +1362,6 @@ class AllRegisterObjectsView(PermissionRequiredMixin, View):
         return render(request, "alsijil/class_register/all_objects.html", context)
 
 
-class CoursebookView(PermissionRequiredMixin, DetailView):
-    model = Lesson
-    template_name = "alsijil/class_register/coursebook.html"
-    permission_required = "alsijil.view_coursebook_rule"
-
-    def get_context_data(self, **kwargs):
-        context = super().get_context_data(**kwargs)
-        return context
-
-
 class SelectCoursebookView(PermissionRequiredMixin, TemplateView):
     template_name = "alsijil/class_register/select_coursebook.html"
     permission_required = "alsijil.view_coursebook_rule"  # FIXME