diff --git a/aleksis/apps/chronos/frontend/components/Timetable.vue b/aleksis/apps/chronos/frontend/components/Timetable.vue
index 83b13708fb6a2ed38c9cd3ee7a8c34f129e1ffee..fbbaaddbb90d8912d0271c0d4ff1f8d97696b0af 100644
--- a/aleksis/apps/chronos/frontend/components/Timetable.vue
+++ b/aleksis/apps/chronos/frontend/components/Timetable.vue
@@ -1,203 +1,19 @@
+<script setup>
+import TimetableWrapper from "./TimetableWrapper.vue";
+</script>
 <script>
-import { gqlAvailableTimetables } from "./timetables.graphql";
-import NoTimetableCard from "./NoTimetableCard.vue";
-import SelectTimetable from "./SelectTimetable.vue";
-import timetableTypes from "./timetableTypes";
-
 export default {
   name: "Timetable",
-  components: { NoTimetableCard, SelectTimetable },
-  apollo: {
-    availableTimetables: {
-      query: gqlAvailableTimetables,
-      result() {
-        if (
-          !this.selected &&
-          this.$route.params.id &&
-          this.$route.params.type
-        ) {
-          this.selectTimetable(
-            this.availableTimetables.find(
-              (t) =>
-                t.objId === this.$route.params.id &&
-                t.type.toLowerCase() === this.$route.params.type,
-            ),
-          );
-        }
-      },
-    },
-  },
-  data() {
-    return {
-      availableTimetables: [],
-      selected: null,
-      search: "",
-      selectedTypes: ["GROUP", "TEACHER", "ROOM"],
-      types: timetableTypes,
-      selectDialog: false,
-    };
-  },
-  watch: {
-    selected(selected) {
-      // Align navigation with currently selected timetable
-      if (!selected) {
-        this.$router.push({ name: "chronos.timetable" });
-      } else if (
-        selected.objId !== this.$route.params.id ||
-        selected.type.toLowerCase() !== this.$route.params.type
-      ) {
-        this.$router.push({
-          name: "chronos.timetableWithId",
-          params: {
-            type: selected.type.toLowerCase(),
-            id: selected.objId,
-          },
-        });
-      }
-    },
-  },
-  methods: {
-    findNextTimetable(offset = 1) {
-      const currentIndex = this.availableTimetablesIds.indexOf(
-        this.selected.id,
-      );
-      const newIndex = currentIndex + offset;
-      if (newIndex < 0 || newIndex >= this.availableTimetablesIds.length) {
-        return null;
-      }
-      return this.availableTimetables[newIndex];
-    },
-    selectTimetable(timetable) {
-      this.selected = timetable;
-    },
-  },
-  computed: {
-    selectedTypesFull() {
-      return this.selectedTypes.map((type) => {
-        return this.types[type];
-      });
-    },
-    availableTimetablesFiltered() {
-      // Filter timetables by selected types
-      return this.availableTimetables.filter((timetable) => {
-        return this.selectedTypes.indexOf(timetable.type) !== -1;
-      });
-    },
-    availableTimetablesIds() {
-      return this.availableTimetables.map((timetable) => timetable.id);
-    },
-    prevTimetable() {
-      return this.findNextTimetable(-1);
-    },
-    nextTimetable() {
-      return this.findNextTimetable(1);
-    },
-  },
 };
 </script>
 
 <template>
-  <div>
-    <v-row>
-      <v-dialog
-        v-model="selectDialog"
-        fullscreen
-        hide-overlay
-        transition="dialog-bottom-transition"
-      >
-        <v-card>
-          <v-toolbar dark color="primary">
-            <v-toolbar-title>{{
-              $t("chronos.timetable.select")
-            }}</v-toolbar-title>
-            <v-spacer></v-spacer>
-          </v-toolbar>
-          <select-timetable
-            v-model="selected"
-            @input="selectDialog = false"
-            :available-timetables="availableTimetables"
-          />
-        </v-card>
-      </v-dialog>
-
-      <v-col md="3" lg="3" xl="3" v-if="$vuetify.breakpoint.lgAndUp">
-        <v-card>
-          <select-timetable
-            v-model="selected"
-            :available-timetables="availableTimetables"
-          />
-        </v-card>
-      </v-col>
-      <v-col sm="12" md="12" lg="9" xl="9" class="full-height">
-        <!-- No timetable card-->
-        <no-timetable-card
-          v-if="selected == null"
-          @selectTimetable="selectDialog = true"
-        />
-
-        <!-- Calendar card-->
-        <v-card v-else>
-          <div class="d-flex flex-column" v-if="$vuetify.breakpoint.smAndDown">
-            <v-card-title class="pt-2">
-              <v-btn
-                icon
-                :disabled="!prevTimetable"
-                @click="selectTimetable(prevTimetable)"
-                :title="$t('chronos.timetable.prev')"
-                class="mr-1"
-              >
-                <v-icon>mdi-chevron-left</v-icon>
-              </v-btn>
-              <v-spacer />
-              <v-chip outlined color="secondary" @click="selectDialog = true">
-                {{ selected.name }}
-                <v-icon right>mdi-chevron-down</v-icon>
-              </v-chip>
-              <v-spacer />
-              <v-btn
-                icon
-                :disabled="!nextTimetable"
-                @click="selectTimetable(nextTimetable)"
-                :title="$t('chronos.timetable.next')"
-                class="ml-1 float-right"
-              >
-                <v-icon>mdi-chevron-right</v-icon>
-              </v-btn>
-            </v-card-title>
-          </div>
-
-          <div class="d-flex flex-wrap justify-space-between mb-2" v-else>
-            <v-card-title>
-              {{ selected.name }}
-            </v-card-title>
-            <div class="pa-2 mt-1">
-              <v-btn
-                icon
-                :disabled="!prevTimetable"
-                @click="selectTimetable(prevTimetable)"
-                :title="$t('chronos.timetable.prev')"
-              >
-                <v-icon>mdi-chevron-left</v-icon>
-              </v-btn>
-              <v-chip label color="secondary" outlined class="mx-1">{{
-                selected.shortName
-              }}</v-chip>
-              <v-btn
-                icon
-                :disabled="!nextTimetable"
-                @click="selectTimetable(nextTimetable)"
-                :title="$t('chronos.timetable.next')"
-              >
-                <v-icon>mdi-chevron-right</v-icon>
-              </v-btn>
-            </div>
-          </div>
-          <calendar-with-controls
-            :calendar-feeds="[{ name: 'lesson' }, { name: 'supervision' }]"
-            :params="{ type: selected.type, id: selected.objId }"
-          />
-        </v-card>
-      </v-col>
-    </v-row>
-  </div>
+  <timetable-wrapper>
+    <template #default="{ selected }">
+      <calendar-with-controls
+        :calendar-feeds="[{ name: 'lesson' }, { name: 'supervision' }]"
+        :params="{ type: selected.type, id: selected.objId }"
+      />
+    </template>
+  </timetable-wrapper>
 </template>
diff --git a/aleksis/apps/chronos/frontend/components/TimetableWrapper.vue b/aleksis/apps/chronos/frontend/components/TimetableWrapper.vue
new file mode 100644
index 0000000000000000000000000000000000000000..d3228681ac045848c0d04c98b96f01e7e7bda508
--- /dev/null
+++ b/aleksis/apps/chronos/frontend/components/TimetableWrapper.vue
@@ -0,0 +1,234 @@
+<script>
+import { gqlAvailableTimetables } from "./timetables.graphql";
+import NoTimetableCard from "./NoTimetableCard.vue";
+import SelectTimetable from "./SelectTimetable.vue";
+import timetableTypes from "./timetableTypes";
+
+export default {
+  name: "TimetableWrapper",
+  components: { NoTimetableCard, SelectTimetable },
+  apollo: {
+    availableTimetables: {
+      query: gqlAvailableTimetables,
+      result() {
+        if (
+          !this.selected &&
+          this.$route.params.id &&
+          this.$route.params.type
+        ) {
+          this.selectTimetable(
+            this.availableTimetables.find(
+              (t) =>
+                t.objId === this.$route.params.id &&
+                t.type.toLowerCase() === this.$route.params.type,
+            ),
+          );
+        }
+      },
+    },
+  },
+  data() {
+    return {
+      availableTimetables: [],
+      selected: null,
+      search: "",
+      selectedTypes: ["GROUP", "TEACHER", "ROOM"],
+      types: timetableTypes,
+      selectDialog: false,
+    };
+  },
+  props: {
+    onSelected: {
+      type: Function,
+      required: false,
+      default: null,
+    },
+  },
+  watch: {
+    selected(selected) {
+      if (this.onSelected) {
+        this.onSelected(selected);
+        return;
+      }
+      // Align navigation with currently selected timetable
+      if (!selected) {
+        this.$router.push({ name: "chronos.timetable" });
+      } else if (
+        selected.objId !== this.$route.params.id ||
+        selected.type.toLowerCase() !== this.$route.params.type
+      ) {
+        this.$router.push({
+          name: "chronos.timetableWithId",
+          params: {
+            type: selected.type.toLowerCase(),
+            id: selected.objId,
+          },
+        });
+      }
+    },
+  },
+  methods: {
+    findNextTimetable(offset = 1) {
+      const currentIndex = this.availableTimetablesIds.indexOf(
+        this.selected.id,
+      );
+      const newIndex = currentIndex + offset;
+      if (newIndex < 0 || newIndex >= this.availableTimetablesIds.length) {
+        return null;
+      }
+      return this.availableTimetables[newIndex];
+    },
+    selectTimetable(timetable) {
+      this.selected = timetable;
+    },
+  },
+  computed: {
+    selectedTypesFull() {
+      return this.selectedTypes.map((type) => {
+        return this.types[type];
+      });
+    },
+    availableTimetablesFiltered() {
+      // Filter timetables by selected types
+      return this.availableTimetables.filter((timetable) => {
+        return this.selectedTypes.indexOf(timetable.type) !== -1;
+      });
+    },
+    availableTimetablesIds() {
+      return this.availableTimetables.map((timetable) => timetable.id);
+    },
+    prevTimetable() {
+      return this.findNextTimetable(-1);
+    },
+    nextTimetable() {
+      return this.findNextTimetable(1);
+    },
+  },
+};
+</script>
+
+<template>
+  <div>
+    <v-row>
+      <v-dialog
+        v-model="selectDialog"
+        fullscreen
+        hide-overlay
+        transition="dialog-bottom-transition"
+      >
+        <v-card>
+          <v-toolbar dark color="primary">
+            <v-btn icon dark @click="selectDialog = false">
+              <v-icon>mdi-close</v-icon>
+            </v-btn>
+            <v-toolbar-title>{{
+              $t("chronos.timetable.select")
+            }}</v-toolbar-title>
+            <v-spacer></v-spacer>
+          </v-toolbar>
+          <slot
+            name="additionalSelect"
+            :selected="selected"
+            :mobile="true"
+          ></slot>
+          <select-timetable
+            v-model="selected"
+            @input="selectDialog = false"
+            :available-timetables="availableTimetables"
+          />
+        </v-card>
+      </v-dialog>
+
+      <v-col md="3" lg="3" xl="3" v-if="$vuetify.breakpoint.lgAndUp">
+        <slot
+          name="additionalSelect"
+          :selected="selected"
+          :mobile="false"
+        ></slot>
+        <v-card>
+          <select-timetable
+            v-model="selected"
+            :available-timetables="availableTimetables"
+          />
+        </v-card>
+      </v-col>
+      <v-col sm="12" md="12" lg="9" xl="9" class="full-height">
+        <!-- No timetable card-->
+        <no-timetable-card
+          v-if="selected == null"
+          @selectTimetable="selectDialog = true"
+        />
+
+        <!-- Calendar card-->
+        <v-card v-else>
+          <div class="d-flex flex-column" v-if="$vuetify.breakpoint.smAndDown">
+            <v-card-title class="pt-2">
+              <v-btn
+                icon
+                :disabled="!prevTimetable"
+                @click="selectTimetable(prevTimetable)"
+                :title="$t('chronos.timetable.prev')"
+                class="mr-1"
+              >
+                <v-icon>mdi-chevron-left</v-icon>
+              </v-btn>
+              <v-spacer />
+              <v-chip outlined color="secondary" @click="selectDialog = true">
+                {{ selected.name }}
+                <v-icon right>mdi-chevron-down</v-icon>
+              </v-chip>
+              <v-spacer />
+              <v-btn
+                icon
+                :disabled="!nextTimetable"
+                @click="selectTimetable(nextTimetable)"
+                :title="$t('chronos.timetable.next')"
+                class="ml-1 float-right"
+              >
+                <v-icon>mdi-chevron-right</v-icon>
+              </v-btn>
+            </v-card-title>
+            <slot
+              name="additionalButton"
+              :selected="selected"
+              :mobile="true"
+            ></slot>
+          </div>
+
+          <div class="d-flex flex-wrap justify-space-between mb-2" v-else>
+            <v-card-title>
+              {{ selected.name }}
+              <slot
+                name="additionalButton"
+                :selected="selected"
+                :mobile="false"
+              ></slot>
+            </v-card-title>
+            <div class="pa-2 mt-1">
+              <v-btn
+                icon
+                :disabled="!prevTimetable"
+                @click="selectTimetable(prevTimetable)"
+                :title="$t('chronos.timetable.prev')"
+              >
+                <v-icon>mdi-chevron-left</v-icon>
+              </v-btn>
+              <v-chip label color="secondary" outlined class="mx-1">{{
+                selected.shortName
+              }}</v-chip>
+              <v-btn
+                icon
+                :disabled="!nextTimetable"
+                @click="selectTimetable(nextTimetable)"
+                :title="$t('chronos.timetable.next')"
+              >
+                <v-icon>mdi-chevron-right</v-icon>
+              </v-btn>
+            </div>
+          </div>
+          <slot :selected="selected"></slot>
+        </v-card>
+      </v-col>
+    </v-row>
+  </div>
+</template>
diff --git a/aleksis/apps/chronos/frontend/index.js b/aleksis/apps/chronos/frontend/index.js
index df633c3f25a0a63864551479aec686398e091ad7..2080d78cfb8899c06979143304062b724a63d068 100644
--- a/aleksis/apps/chronos/frontend/index.js
+++ b/aleksis/apps/chronos/frontend/index.js
@@ -11,9 +11,6 @@ export default {
     iconActive: "mdi-school",
     validators: [hasPersonValidator],
   },
-  props: {
-    byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true,
-  },
   children: [
     {
       path: "timetable/",
@@ -22,6 +19,7 @@ export default {
       meta: {
         inMenu: true,
         titleKey: "chronos.timetable.menu_title",
+        toolbarTitle: "chronos.timetable.menu_title",
         icon: "mdi-grid",
         permission: "chronos.view_timetable_overview_rule",
         fullWidth: true,
diff --git a/aleksis/apps/chronos/managers.py b/aleksis/apps/chronos/managers.py
index 72b9a30645eebbeba469f33fa886143b3370bde0..dd63807aa5ebee41234a9eaff786b6cb6a5e2e06 100644
--- a/aleksis/apps/chronos/managers.py
+++ b/aleksis/apps/chronos/managers.py
@@ -876,6 +876,15 @@ class LessonEventQuerySet(PolymorphicQuerySet):
         )
         return self.filter(Q(teachers=teacher) | Q(pk__in=amended)).distinct()
 
+    def for_participant(self, person: Union[int, Person]) -> "LessonEventQuerySet":
+        """Get all lesson events the person participates in (including amends)."""
+        amended = self.filter(
+            Q(amended_by__isnull=False) | Q(groups__members=person)
+        ).values_list("amended_by__pk", flat=True)
+        return self.filter(
+            Q(groups__members=person) | Q(pk__in=amended)
+        ).distinct()
+
     def for_group(self, group: Union[int, Group]) -> "LessonEventQuerySet":
         """Get all lesson events for a certain group (including amends/as parent group)."""
         amended = self.filter(
diff --git a/aleksis/apps/chronos/models.py b/aleksis/apps/chronos/models.py
index 051e6ccecd275aa649535256794e6e06d1359899..d27e4c6a037634afbe1cc43401ab10bbce6203cf 100644
--- a/aleksis/apps/chronos/models.py
+++ b/aleksis/apps/chronos/models.py
@@ -1559,6 +1559,8 @@ class LessonEvent(CalendarEvent):
             if type_ and obj_id:
                 if type_ == "TEACHER":
                     return objs.for_teacher(obj_id)
+                elif type_ == "PARTICIPANT":
+                    return objs.for_participant(obj_id)
                 elif type_ == "GROUP":
                     return objs.for_group(obj_id)
                 elif type_ == "ROOM":
diff --git a/aleksis/apps/chronos/util/chronos_helpers.py b/aleksis/apps/chronos/util/chronos_helpers.py
index e5d5fe9c340f4a491679bf69d4d3abb358e2787d..8e1640150383c7c00cfb145c74c9b8750c7a0c29 100644
--- a/aleksis/apps/chronos/util/chronos_helpers.py
+++ b/aleksis/apps/chronos/util/chronos_helpers.py
@@ -35,9 +35,6 @@ def get_el_by_pk(
     request: HttpRequest,
     type_: str,
     pk: int,
-    year: Optional[int] = None,
-    week: Optional[int] = None,
-    regular: Optional[str] = None,
     prefetch: bool = False,
     *args,
     **kwargs,