diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/Coursebook.vue b/aleksis/apps/alsijil/frontend/components/coursebook/Coursebook.vue
index e2f56912c65ac40dc7cecc9da5342ed8ff02928e..650e1aeb90810a9fa5f3366e5e9db59a00daca58 100644
--- a/aleksis/apps/alsijil/frontend/components/coursebook/Coursebook.vue
+++ b/aleksis/apps/alsijil/frontend/components/coursebook/Coursebook.vue
@@ -6,37 +6,54 @@
     :enable-create="false"
     :enable-edit="false"
     :elevated="false"
+    :items-per-page="-1"
     @lastQuery="lastQuery = $event"
     ref="iterator"
     hide-default-footer
   >
     <template #additionalActions="{ attrs, on }">
-      <v-autocomplete
-        :items="selectable"
-        item-text="name"
-        clearable
-        return-object
-        filled
-        dense
-        hide-details
-        :placeholder="$t('alsijil.coursebook.filter.filter_for_obj')"
-        :loading="selectLoading"
-        :value="currentObj"
-        @input="changeSelection"
-        @click:clear="changeSelection"
-      />
-      <v-switch
-        :loading="selectLoading"
-        :label="$t('alsijil.coursebook.filter.own')"
-        :input-value="filterType === 'my'"
-        @change="
-          changeSelection({
-            filterType: $event ? 'my' : 'all',
-            type: objType,
-            id: objId,
-          })
-        "
-      />
+      <div class="d-flex flex-grow-1 justify-end">
+        <v-autocomplete
+          :items="selectable"
+          item-text="name"
+          clearable
+          return-object
+          filled
+          dense
+          hide-details
+          :placeholder="$t('alsijil.coursebook.filter.filter_for_obj')"
+          :loading="selectLoading"
+          :value="currentObj"
+          @input="changeSelection"
+          @click:clear="changeSelection"
+          class="max-width"
+        />
+        <div class="ml-6">
+          <v-switch
+            :loading="selectLoading"
+            :label="$t('alsijil.coursebook.filter.own')"
+            :input-value="filterType === 'my'"
+            @change="
+              changeSelection({
+                filterType: $event ? 'my' : 'all',
+                type: objType,
+                id: objId,
+              })
+            "
+            dense
+            inset
+            hide-details
+          />
+          <v-switch
+            :loading="selectLoading"
+            :label="$t('alsijil.coursebook.filter.missing')"
+            v-model="incomplete"
+            dense
+            inset
+            hide-details
+          />
+        </div>
+      </div>
     </template>
     <template #default="{ items }">
       <v-list-item
@@ -45,11 +62,13 @@
         :key="'day-' + day[0]"
       >
         <v-list-item-content :id="'documentation_' + day[0].toISODate()">
-          <v-subheader class="text-h6">{{ $d(day[0], "dateWithWeekday") }}</v-subheader>
+          <v-subheader class="text-h6">{{
+            $d(day[0], "dateWithWeekday")
+          }}</v-subheader>
           <v-list max-width="100%" class="pt-0 mt-n1">
             <v-list-item
               v-for="doc in day.slice(1)"
-              :key="'documentation-' + doc.id"
+              :key="'documentation-' + (doc.oldId || doc.id)"
             >
               <documentation-modal
                 :documentation="doc"
@@ -137,6 +156,7 @@ export default {
       courses: [],
       dateStart: null,
       dateEnd: null,
+      incomplete: false,
     };
   },
   apollo: {
@@ -158,6 +178,7 @@ export default {
         dateEnd:
           this.dateEnd ??
           DateTime.fromISO(this.date).plus({ weeks: 1 }).toISODate(),
+        incomplete: !!this.incomplete,
       };
     },
     selectable() {
@@ -279,3 +300,9 @@ export default {
   },
 };
 </script>
+
+<style>
+.max-width {
+  max-width: 25rem;
+}
+</style>
diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/CoursebookDateSelect.vue b/aleksis/apps/alsijil/frontend/components/coursebook/CoursebookDateSelect.vue
index 486f2d23438fdbf79b767ed1da7eb21a0e44ff35..933bcb29ba3b63798dd0f853dee58f5a921bae22 100644
--- a/aleksis/apps/alsijil/frontend/components/coursebook/CoursebookDateSelect.vue
+++ b/aleksis/apps/alsijil/frontend/components/coursebook/CoursebookDateSelect.vue
@@ -49,12 +49,7 @@ export default {
 </script>
 
 <template>
-  <v-footer
-    app
-    inset
-    padless
-    id="date-select-footer"
-  >
+  <v-footer app inset padless id="date-select-footer">
     <v-card tile class="full-width">
       <v-card-title id="content">
         <div class="d-flex align-center justify-center full-width">
diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/coursebook.graphql b/aleksis/apps/alsijil/frontend/components/coursebook/coursebook.graphql
index c47203625ce2e9ad1e2dd0a5e828eb7b0d29ce7f..c73f273af1451a5f102d3b91dc66dadce57901ee 100644
--- a/aleksis/apps/alsijil/frontend/components/coursebook/coursebook.graphql
+++ b/aleksis/apps/alsijil/frontend/components/coursebook/coursebook.graphql
@@ -22,6 +22,7 @@ query documentationsForCoursebook(
   $objType: String
   $dateStart: Date!
   $dateEnd: Date!
+  $incomplete: Boolean
 ) {
   items: documentationsForCoursebook(
     own: $own
@@ -29,6 +30,7 @@ query documentationsForCoursebook(
     objType: $objType
     dateStart: $dateStart
     dateEnd: $dateEnd
+    incomplete: $incomplete
   ) {
     id
     course {
@@ -37,6 +39,10 @@ query documentationsForCoursebook(
     }
     lessonEvent {
       id
+      amends {
+        id
+      }
+      cancelled
     }
     teachers {
       id
@@ -58,7 +64,9 @@ query documentationsForCoursebook(
     datetimeEnd
     dateStart
     dateEnd
+    oldId
     canEdit
+    futureNotice
     canDelete
   }
 }
@@ -70,6 +78,7 @@ mutation createOrUpdateDocumentations($input: [DocumentationInputType]!) {
       topic
       homework
       groupNote
+      oldId
     }
   }
 }
diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/documentation/Documentation.vue b/aleksis/apps/alsijil/frontend/components/coursebook/documentation/Documentation.vue
index 0f767b923cddbed44f1bf7e3e48abc6b192f9f86..5bbc21c0de53b613986c30360e853ee6720526b5 100644
--- a/aleksis/apps/alsijil/frontend/components/coursebook/documentation/Documentation.vue
+++ b/aleksis/apps/alsijil/frontend/components/coursebook/documentation/Documentation.vue
@@ -1,12 +1,15 @@
 <template>
-  <v-card :class="{'my-1 full-width': true, 'd-flex flex-column': !compact }">
+  <v-card :class="{ 'my-1 full-width': true, 'd-flex flex-column': !compact }">
     <v-card-title v-if="!compact">
       <lesson-information v-bind="documentationPartProps" />
     </v-card-title>
 
     <v-card-text
       class="full-width main-body"
-      :class="{ 'vertical': !compact || $vuetify.breakpoint.mobile, 'pa-2': compact }"
+      :class="{
+        vertical: !compact || $vuetify.breakpoint.mobile,
+        'pa-2': compact,
+      }"
     >
       <lesson-information v-if="compact" v-bind="documentationPartProps" />
       <lesson-summary
@@ -24,9 +27,21 @@
     <v-divider />
     <v-card-actions v-if="!compact">
       <v-spacer />
-      <cancel-button v-if="documentation.canEdit" @click="$emit('close')" :disabled="loading" />
-      <save-button v-if="documentation.canEdit" @click="save" :loading="loading" />
-      <cancel-button v-if="!documentation.canEdit" i18n-key="actions.close" @click="$emit('close')"/>
+      <cancel-button
+        v-if="documentation.canEdit"
+        @click="$emit('close')"
+        :disabled="loading"
+      />
+      <save-button
+        v-if="documentation.canEdit"
+        @click="save"
+        :loading="loading"
+      />
+      <cancel-button
+        v-if="!documentation.canEdit"
+        i18n-key="actions.close"
+        @click="$emit('close')"
+      />
     </v-card-actions>
   </v-card>
 </template>
diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/documentation/DocumentationCompactDetails.vue b/aleksis/apps/alsijil/frontend/components/coursebook/documentation/DocumentationCompactDetails.vue
new file mode 100644
index 0000000000000000000000000000000000000000..9160bd79522900613c4ac195b9bcf85d818fb9d3
--- /dev/null
+++ b/aleksis/apps/alsijil/frontend/components/coursebook/documentation/DocumentationCompactDetails.vue
@@ -0,0 +1,30 @@
+<template>
+  <v-card outlined dense rounded="lg" v-bind="$attrs" v-on="$listeners">
+    <div class="font-weight-medium mr-2">
+      {{ $t("alsijil.coursebook.summary.topic") }}:
+    </div>
+    <div class="text-truncate">{{ documentation.topic || "–" }}</div>
+
+    <div class="font-weight-medium mr-2">
+      {{ $t("alsijil.coursebook.summary.homework.label") }}:
+    </div>
+    <div class="text-truncate">{{ documentation.homework || "–" }}</div>
+
+    <div class="font-weight-medium mr-2">
+      {{ $t("alsijil.coursebook.summary.group_note.label") }}:
+    </div>
+    <div class="text-truncate">{{ documentation.groupNote || "–" }}</div>
+  </v-card>
+</template>
+
+<script>
+export default {
+  name: "DocumentationCompactDetails",
+  props: {
+    documentation: {
+      type: Object,
+      required: true,
+    },
+  },
+};
+</script>
diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/documentation/DocumentationFullDetails.vue b/aleksis/apps/alsijil/frontend/components/coursebook/documentation/DocumentationFullDetails.vue
new file mode 100644
index 0000000000000000000000000000000000000000..ddb7ae2764771ac59eb5e69f6e2ffb5ee6c7725b
--- /dev/null
+++ b/aleksis/apps/alsijil/frontend/components/coursebook/documentation/DocumentationFullDetails.vue
@@ -0,0 +1,34 @@
+<template>
+  <div v-bind="$attrs" v-on="$listeners">
+    <v-card outlined dense rounded="lg" class="mb-2">
+      <v-card-title class="text-subtitle-2 pb-1 font-weight-medium">
+        {{ $t("alsijil.coursebook.summary.topic") }}
+      </v-card-title>
+      <v-card-text>{{ documentation.topic || "–" }}</v-card-text>
+    </v-card>
+    <v-card outlined dense rounded="lg" class="mb-2">
+      <v-card-title class="text-subtitle-2 pb-1 font-weight-medium">
+        {{ $t("alsijil.coursebook.summary.homework.label") }}
+      </v-card-title>
+      <v-card-text>{{ documentation.homework || "–" }}</v-card-text>
+    </v-card>
+    <v-card outlined dense rounded="lg">
+      <v-card-title class="text-subtitle-2 pb-1 font-weight-medium">
+        {{ $t("alsijil.coursebook.summary.group_note.label") }}
+      </v-card-title>
+      <v-card-text>{{ documentation.groupNote || "–" }}</v-card-text>
+    </v-card>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "DocumentationFullDetails",
+  props: {
+    documentation: {
+      type: Object,
+      required: true,
+    },
+  },
+};
+</script>
diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/documentation/DocumentationModal.vue b/aleksis/apps/alsijil/frontend/components/coursebook/documentation/DocumentationModal.vue
index 08f5b0ea04ff60677dbf5bc415d120f5721dca8a..460f39f97fc15b61d0476dd99ed33d29cf43029c 100644
--- a/aleksis/apps/alsijil/frontend/components/coursebook/documentation/DocumentationModal.vue
+++ b/aleksis/apps/alsijil/frontend/components/coursebook/documentation/DocumentationModal.vue
@@ -4,11 +4,7 @@
   <mobile-fullscreen-dialog v-model="popup" max-width="500px">
     <template #activator="activator">
       <!-- list view -> activate dialog -->
-      <documentation
-        compact
-        v-bind="$attrs"
-        :dialog-activator="activator"
-      />
+      <documentation compact v-bind="$attrs" :dialog-activator="activator" />
     </template>
     <!-- dialog view -> deactivate dialog -->
     <!-- cancel | save (through lesson-summary) -->
diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/documentation/DocumentationStatus.vue b/aleksis/apps/alsijil/frontend/components/coursebook/documentation/DocumentationStatus.vue
index d9c3950e40ff821fdc3cdd3df4b54382b35ecd41..4b44fa537a25f9d0d9848014e8f3580ab330ff4d 100644
--- a/aleksis/apps/alsijil/frontend/components/coursebook/documentation/DocumentationStatus.vue
+++ b/aleksis/apps/alsijil/frontend/components/coursebook/documentation/DocumentationStatus.vue
@@ -1,7 +1,13 @@
 <template>
   <v-tooltip bottom>
     <template v-slot:activator="{ on, attrs }">
-      <v-icon :color="currentStatus?.color" class="mr-md-4" v-on="on" v-bind="attrs">{{ currentStatus?.icon }}</v-icon>
+      <v-icon
+        :color="currentStatus?.color"
+        class="mr-md-4"
+        v-on="on"
+        v-bind="attrs"
+        >{{ currentStatus?.icon }}</v-icon
+      >
     </template>
     <span>{{ currentStatus?.text }}</span>
   </v-tooltip>
@@ -45,7 +51,7 @@ export default {
         {
           name: "cancelled",
           text: this.$t("alsijil.coursebook.status.cancelled"),
-          icon: "$cancel",
+          icon: "mdi-cancel",
           color: "error",
         },
         {
@@ -72,33 +78,55 @@ export default {
   },
   methods: {
     updateStatus() {
-      if (!this.documentation.id.startsWith("DUMMY")) {
+      if (this.documentation?.lessonEvent.cancelled) {
+        this.currentStatusName = "cancelled";
+      } else if (this.documentation.topic) {
         this.currentStatusName = "available";
+      } else if (DateTime.now() > this.documentationDateTimeEnd) {
+        this.currentStatusName = "missing";
+      } else if (this.documentation?.lessonEvent.amends) {
+        this.currentStatusName = "substitution";
+      } else if (
+        DateTime.now() > this.documentationDateTimeStart &&
+        DateTime.now() < this.documentationDateTimeEnd
+      ) {
+        this.currentStatusName = "running";
       } else {
-        if (DateTime.now() > this.documentationDateTimeEnd) {
-          this.currentStatusName = "missing";
-        } else if (DateTime.now() > this.documentationDateTimeStart && DateTime.now() < this.documentationDateTimeEnd) {
-          this.currentStatusName = "running";
-        } else {
-          if (this.documentation?.lessonEvent.amends) {
-            if (this.documentation.lessonEvent.amends.cancelled) {
-              this.currentStatusName = "cancelled";
-            } else {
-              this.currentStatusName = "substitution";
-            }
-          } else {
-            this.currentStatusName = "pending";
-          }
-        }
+        this.currentStatusName = "pending";
       }
     },
   },
+  watch: {
+    documentation: {
+      handler() {
+        this.updateStatus();
+      },
+      deep: true,
+    },
+  },
   mounted() {
     this.updateStatus();
-    this.statusTimeout = setTimeout(this.updateStatus, this.documentationDateTimeStart.diff(DateTime.now(), "seconds").toObject().seconds);
+
+    if (DateTime.now() < this.documentationDateTimeStart) {
+      this.statusTimeout = setTimeout(
+        this.updateStatus,
+        this.documentationDateTimeStart
+          .diff(DateTime.now(), "seconds")
+          .toObject(),
+      );
+    } else if (DateTime.now() < this.documentationDateTimeEnd) {
+      this.statusTimeout = setTimeout(
+        this.updateStatus,
+        this.documentationDateTimeEnd
+          .diff(DateTime.now(), "seconds")
+          .toObject(),
+      );
+    }
   },
   beforeDestroy() {
-    clearTimeout(this.statusTimeout);
+    if (this.statusTimeout) {
+      clearTimeout(this.statusTimeout);
+    }
   },
-}
+};
 </script>
diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/documentation/LessonInformation.vue b/aleksis/apps/alsijil/frontend/components/coursebook/documentation/LessonInformation.vue
index 747b666eab1e59cf05be75a2e6012a5e59699e31..6e709e24493049028ecc99ea55b874bab417da26 100644
--- a/aleksis/apps/alsijil/frontend/components/coursebook/documentation/LessonInformation.vue
+++ b/aleksis/apps/alsijil/frontend/components/coursebook/documentation/LessonInformation.vue
@@ -17,7 +17,13 @@ import PersonChip from "aleksis.core/components/person/PersonChip.vue";
         </time>
       </div>
     </div>
-    <span :class="{ 'text-right': !largeGrid }">
+    <span
+      :class="{
+        'text-right': !largeGrid,
+        'text-subtitle-1': largeGrid,
+        'font-weight-medium': largeGrid,
+      }"
+    >
       {{ documentation.course?.name }}
     </span>
     <subject-chip
@@ -57,7 +63,7 @@ export default {
   computed: {
     largeGrid() {
       return this.compact && !this.$vuetify.breakpoint.mobile;
-    }
+    },
   },
 };
 </script>
diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/documentation/LessonSummary.vue b/aleksis/apps/alsijil/frontend/components/coursebook/documentation/LessonSummary.vue
index ad9a626490f3c6fe8dc39342f21222dee70cc9ab..47bca3b28fbc36b092112147c317ded8c6e50eb0 100644
--- a/aleksis/apps/alsijil/frontend/components/coursebook/documentation/LessonSummary.vue
+++ b/aleksis/apps/alsijil/frontend/components/coursebook/documentation/LessonSummary.vue
@@ -5,8 +5,45 @@
       class="d-flex flex-column flex-md-row align-stretch align-md-center gap justify-start fill-height"
       v-if="compact"
     >
+      <documentation-compact-details
+        v-bind="dialogActivator.attrs"
+        v-on="dialogActivator.on"
+        v-if="
+          !documentation.canEdit &&
+          (documentation.topic ||
+            documentation.homework ||
+            documentation.groupNote)
+        "
+        :documentation="documentation"
+        @click="$emit('open')"
+        :class="{
+          'flex-grow-1 min-width pa-1 read-only-grid': true,
+          'full-width': $vuetify.breakpoint.mobile,
+        }"
+      />
+      <v-alert
+        v-else-if="documentation.futureNotice"
+        type="warning"
+        outlined
+        class="min-width flex-grow-1 mb-0"
+      >
+        {{ $t("alsijil.coursebook.notices.future") }}
+      </v-alert>
+      <v-alert
+        v-else-if="!documentation.canEdit"
+        type="info"
+        outlined
+        class="min-width flex-grow-1 mb-0"
+      >
+        {{ $t("alsijil.coursebook.notices.no_entry") }}
+      </v-alert>
+
       <v-text-field
-        :class="{ 'flex-grow-1 min-width': true, 'full-width': $vuetify.breakpoint.mobile }"
+        v-if="documentation.canEdit"
+        :class="{
+          'flex-grow-1 min-width': true,
+          'full-width': $vuetify.breakpoint.mobile,
+        }"
         hide-details
         outlined
         :label="$t('alsijil.coursebook.summary.topic')"
@@ -15,9 +52,22 @@
         @focusout="save"
         @keydown.enter="saveAndBlur"
         :loading="loading"
-        :readonly="!documentation.canEdit"
-      />
-      <div :class="{ 'flex-grow-1 max-width': true, 'full-width': $vuetify.breakpoint.mobile }">
+      >
+        <template #append>
+          <v-scroll-x-transition>
+            <v-icon v-if="appendIcon" :color="appendIconColor">{{
+              appendIcon
+            }}</v-icon>
+          </v-scroll-x-transition>
+        </template>
+      </v-text-field>
+      <div
+        :class="{
+          'flex-grow-1 max-width': true,
+          'full-width': $vuetify.breakpoint.mobile,
+        }"
+        v-if="documentation.canEdit"
+      >
         <v-card
           v-bind="dialogActivator.attrs"
           v-on="dialogActivator.on"
@@ -46,37 +96,44 @@
     <!-- Are focusout & enter enough trigger? -->
     <v-text-field
       filled
-      v-if="!compact"
+      v-if="!compact && documentation.canEdit"
       :label="$t('alsijil.coursebook.summary.topic')"
       :value="documentation.topic"
       @input="topic = $event"
-      :readonly="!documentation.canEdit"
     />
     <v-textarea
       filled
       auto-grow
       rows="3"
       clearable
-      v-if="!compact"
+      v-if="!compact && documentation.canEdit"
       :label="$t('alsijil.coursebook.summary.homework.label')"
       :value="documentation.homework"
       @input="homework = $event"
-      :readonly="!documentation.canEdit"
     />
     <v-textarea
       filled
       auto-grow
       rows="3"
       clearable
-      v-if="!compact"
+      v-if="!compact && documentation.canEdit"
       :label="$t('alsijil.coursebook.summary.group_note.label')"
       :value="documentation.groupNote"
       @input="groupNote = $event"
-      :readonly="!documentation.canEdit"
+    />
+
+    <documentation-full-details
+      v-if="!compact && !documentation.canEdit"
+      :documentation="documentation"
     />
   </div>
 </template>
 
+<script setup>
+import DocumentationCompactDetails from "./DocumentationCompactDetails.vue";
+import DocumentationFullDetails from "./DocumentationFullDetails.vue";
+</script>
+
 <script>
 import createOrPatchMixin from "aleksis.core/mixins/createOrPatchMixin.js";
 import documentationPartMixin from "./documentationPartMixin";
@@ -90,6 +147,7 @@ export default {
       topic: null,
       homework: null,
       groupNote: null,
+      appendIcon: null,
     };
   },
   methods: {
@@ -102,16 +160,36 @@ export default {
             (o) => o[itemId] === this.documentation.id,
           );
           // merged with the incoming partial documentation
-          cached[index] = { ...this.documentation, ...object };
+          // if creation of proper documentation from dummy one, set ID of documentation currently being edited as oldID so that key in coursebook doesn't change
+          cached[index] = {
+            ...this.documentation,
+            ...object,
+            oldId:
+              this.documentation.id !== object.id
+                ? this.documentation.id
+                : this.documentation.oldId,
+          };
         }
         return cached;
       };
     },
+    handleAppendIconSuccess() {
+      this.appendIcon = "$success";
+      setTimeout(() => {
+        this.appendIcon = "";
+      }, 3000);
+    },
     save() {
-      if (this.topic !== null || this.homework !== null || this.groupNote !== null) {
+      if (
+        this.topic !== null ||
+        this.homework !== null ||
+        this.groupNote !== null
+      ) {
         const topic = this.topic !== null ? { topic: this.topic } : {};
-        const homework = this.homework !== null ? { homework: this.homework } : {};
-        const groupNote = this.groupNote !== null ? { groupNote: this.groupNote } : {};
+        const homework =
+          this.homework !== null ? { homework: this.homework } : {};
+        const groupNote =
+          this.groupNote !== null ? { groupNote: this.groupNote } : {};
 
         this.createOrPatch([
           {
@@ -131,6 +209,9 @@ export default {
       this.save();
       event.target.blur();
     },
+    handleError() {
+      this.appendIcon = "$error";
+    },
   },
   computed: {
     homeworkIcon() {
@@ -158,7 +239,15 @@ export default {
     },
     maxWidth() {
       return this.$vuetify.breakpoint.mobile ? "100%" : "20ch";
-    }
+    },
+    appendIconColor() {
+      return (
+        { $success: "success", $error: "error" }[this.appendIcon] || "primary"
+      );
+    },
+  },
+  mounted() {
+    this.$on("save", this.handleAppendIconSuccess);
   },
 };
 </script>
@@ -169,7 +258,7 @@ export default {
 }
 
 .max-width {
-  max-width: v-bind(maxWidth)
+  max-width: v-bind(maxWidth);
 }
 
 .gap {
@@ -180,4 +269,10 @@ export default {
   display: grid;
   grid-template-columns: auto min-content;
 }
+
+.read-only-grid {
+  display: grid;
+  grid-template-columns: min-content auto;
+  grid-template-rows: auto;
+}
 </style>
diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/documentation/documentationPartMixin.js b/aleksis/apps/alsijil/frontend/components/coursebook/documentation/documentationPartMixin.js
index 099878f82cbf3feb0aef8bd588b3aa49144fd8eb..165f1d2fd157bb35bf2831fc7973f480b29ccd0a 100644
--- a/aleksis/apps/alsijil/frontend/components/coursebook/documentation/documentationPartMixin.js
+++ b/aleksis/apps/alsijil/frontend/components/coursebook/documentation/documentationPartMixin.js
@@ -2,43 +2,43 @@
  * Mixin to provide common fields for all components specific to a singular documentation inside the coursebook
  */
 export default {
-    props: {
-        /**
-         * The documentation in question
-         */
-        documentation: {
-            type: Object,
-            required: true,
-        },
-        /**
-         * Whether the documentation is currently in the compact mode (meaning coursebook row)
-         */
-        compact: {
-            type: Boolean,
-            required: false,
-            default: false,
-        },
-        /**
-         * Activator attributes and event listeners to open documentation dialog in different places
-         */
-        dialogActivator: {
-          type: Object,
-          required: false,
-          default: () => ({ attrs: {}, on: {} }),
-        },
+  props: {
+    /**
+     * The documentation in question
+     */
+    documentation: {
+      type: Object,
+      required: true,
     },
+    /**
+     * Whether the documentation is currently in the compact mode (meaning coursebook row)
+     */
+    compact: {
+      type: Boolean,
+      required: false,
+      default: false,
+    },
+    /**
+     * Activator attributes and event listeners to open documentation dialog in different places
+     */
+    dialogActivator: {
+      type: Object,
+      required: false,
+      default: () => ({ attrs: {}, on: {} }),
+    },
+  },
 
-    computed: {
-        /**
-         * All necessary props bundled together to easily pass to child components
-         * @returns {{compact: Boolean, documentation: Object, dialogActivator: Object<{attrs: Object, on: Object}>}}
-         */
-        documentationPartProps() {
-            return {
-                documentation: this.documentation,
-                compact: this.compact,
-                dialogActivator: this.dialogActivator,
-            }
-        }
-    }
+  computed: {
+    /**
+     * All necessary props bundled together to easily pass to child components
+     * @returns {{compact: Boolean, documentation: Object, dialogActivator: Object<{attrs: Object, on: Object}>}}
+     */
+    documentationPartProps() {
+      return {
+        documentation: this.documentation,
+        compact: this.compact,
+        dialogActivator: this.dialogActivator,
+      };
+    },
+  },
 };
diff --git a/aleksis/apps/alsijil/frontend/messages/de.json b/aleksis/apps/alsijil/frontend/messages/de.json
index 586d59fefd034f6008b4ca16c3844d56e0925d3a..614c3142e8d9d59c02b1ed79ead092690a2480b4 100644
--- a/aleksis/apps/alsijil/frontend/messages/de.json
+++ b/aleksis/apps/alsijil/frontend/messages/de.json
@@ -59,9 +59,10 @@
       },
       "filter": {
         "own": "Nur eigene Stunden anzeigen",
-        "groups": "Klassen",
+        "missing": "Nur unvollständige Stunden anzeigen",
+        "groups": "Gruppen",
         "courses": "Kurse",
-        "filter_for_obj": "Nach Klasse und Kurs filtern"
+        "filter_for_obj": "Nach Gruppe und Kurs filtern"
       },
       "no_data": "Keine Stunden der ausgewählten Gruppen und Kurse im aktuellen Zeitraum",
       "no_results": "Keine Suchergebnisse für {search}"
diff --git a/aleksis/apps/alsijil/frontend/messages/en.json b/aleksis/apps/alsijil/frontend/messages/en.json
index 68a3290e6e293307a0294dca198a5d81d36faf29..679044209b95c578e4e89cb439f27745b5e3f9b2 100644
--- a/aleksis/apps/alsijil/frontend/messages/en.json
+++ b/aleksis/apps/alsijil/frontend/messages/en.json
@@ -57,11 +57,16 @@
           "empty": "No group note"
         }
       },
+      "notices": {
+        "future": "Editing this lesson isn't allowed as this lesson is in the future.",
+        "no_entry": "There is no entry for this lesson yet."
+      },
       "filter": {
         "own": "Only show own lessons",
-        "groups": "School classes",
+        "missing": "Only show incomplete lessons",
+        "groups": "Groups",
         "courses": "Courses",
-        "filter_for_obj": "Filter for school class and course"
+        "filter_for_obj": "Filter for group and course"
       },
       "no_data": "No lessons for the selected groups and courses in this period",
       "no_results": "No search results for {search}"
diff --git a/aleksis/apps/alsijil/models.py b/aleksis/apps/alsijil/models.py
index 163e837cbc5cb9949e63b7a13c422369b4ca1e2f..067818d1857c3a4054f9fbb0cc141c0fb0eae1a5 100644
--- a/aleksis/apps/alsijil/models.py
+++ b/aleksis/apps/alsijil/models.py
@@ -1,4 +1,4 @@
-from datetime import date, datetime, timezone
+from datetime import date, datetime
 from typing import Optional, Union
 from urllib.parse import urlparse
 
@@ -481,7 +481,11 @@ class Documentation(CalendarEvent):
     )
 
     teachers = models.ManyToManyField(
-        "core.Person", related_name="documentations_as_teacher", blank=True, null=True, verbose_name=_("Teachers")
+        "core.Person",
+        related_name="documentations_as_teacher",
+        blank=True,
+        null=True,
+        verbose_name=_("Teachers"),
     )
 
     topic = models.CharField(verbose_name=_("Lesson Topic"), max_length=255, blank=True)
@@ -530,8 +534,9 @@ class Documentation(CalendarEvent):
         date_start: datetime,
         date_end: datetime,
         request: HttpRequest,
-        obj_type: Optional[str],
-        obj_id: Optional[str],
+        obj_type: Optional[str] = None,
+        obj_id: Optional[str] = None,
+        incomplete: Optional[bool] = False,
     ) -> list:
         """Get all the documentations for an object and a time frame.
 
@@ -541,7 +546,6 @@ class Documentation(CalendarEvent):
         # 1. Find all LessonEvents for all Lessons of this Course in this date range
         event_params = {
             "own": own,
-            "not_amended": True,
         }
         if obj_type is not None and obj_id is not None:
             event_params.update(
@@ -558,33 +562,38 @@ class Documentation(CalendarEvent):
             event_params,
             with_reference_object=True,
         )
-        # (1.5 filter them by permissions)
-        ...
 
         # 2. For each lessonEvent → check if there is a documentation
         # if so, add the documentation to a list, if not, create a new one
-        return [
-            (
-                existing_documentations.first()
-                if (
-                    existing_documentations := (
-                        event_reference_obj := event["REFERENCE_OBJECT"]
-                    ).documentation.filter(
-                        datetime_start=event["DTSTART"].dt.replace(tzinfo=timezone.utc),
-                        datetime_end=event["DTEND"].dt.replace(tzinfo=timezone.utc),
+        docs = []
+        for event in events:
+            if incomplete and event["STATUS"] == "CANCELLED":
+                continue
+
+            event_reference_obj = event["REFERENCE_OBJECT"]
+            existing_documentations = event_reference_obj.documentation.filter(
+                datetime_start=event["DTSTART"].dt,
+                datetime_end=event["DTEND"].dt,
+            )
+
+            if existing_documentations.exists():
+                doc = existing_documentations.first()
+                if incomplete and doc.topic:
+                    continue
+                docs.append(doc)
+            else:
+                docs.append(
+                    cls(
+                        pk=f"DUMMY;{event_reference_obj.id};{event['DTSTART'].dt.isoformat()};{event['DTEND'].dt.isoformat()}",
+                        lesson_event=event_reference_obj,
+                        course=event_reference_obj.course,
+                        subject=event_reference_obj.subject,
+                        datetime_start=event["DTSTART"].dt,
+                        datetime_end=event["DTEND"].dt,
                     )
-                ).exists()
-                else cls(
-                    pk=f"DUMMY;{event_reference_obj.id};{event['DTSTART'].dt.isoformat()};{event['DTEND'].dt.isoformat()}",
-                    lesson_event=event_reference_obj,
-                    course=event_reference_obj.course,
-                    subject=event_reference_obj.subject,
-                    datetime_start=event["DTSTART"].dt,
-                    datetime_end=event["DTEND"].dt,
                 )
-            )
-            for event in events
-        ]
+
+        return docs
 
 
 class ParticipationStatus(ExtensibleModel):
diff --git a/aleksis/apps/alsijil/preferences.py b/aleksis/apps/alsijil/preferences.py
index 2fd34fa7fc4f802a52ead1d2d0291789be4fb9ce..b00d9277e507e284130340b84cbbca8c07597d1f 100644
--- a/aleksis/apps/alsijil/preferences.py
+++ b/aleksis/apps/alsijil/preferences.py
@@ -2,7 +2,7 @@ from django.core.exceptions import ValidationError
 from django.utils.translation import gettext_lazy as _
 
 from dynamic_preferences.preferences import Section
-from dynamic_preferences.types import BooleanPreference, IntegerPreference
+from dynamic_preferences.types import BooleanPreference, ChoicePreference, IntegerPreference
 
 from aleksis.core.registries import person_preferences_registry, site_preferences_registry
 
@@ -157,3 +157,27 @@ class DefaultLessonDocumentationFilter(BooleanPreference):
     name = "default_lesson_documentation_filter"
     default = True
     verbose_name = _("Filter lessons by existence of their lesson documentation on default")
+
+
+@site_preferences_registry.register
+class AllowEditFutureDocumentations(ChoicePreference):
+    """Time range for which documentations may be edited."""
+
+    section = alsijil
+    name = "allow_edit_future_documentations"
+    default = "current_day"
+    choices = (
+        ("all", _("Allow editing of all future documentations")),
+        (
+            "current_day",
+            _("Allow editing of all documentations up to and including those on the current day"),
+        ),
+        (
+            "current_time",
+            _(
+                "Allow editing of all documentations up to and "
+                "including those on the current date and time"
+            ),
+        ),
+    )
+    verbose_name = _("Set time range for which documentations may be edited")
diff --git a/aleksis/apps/alsijil/rules.py b/aleksis/apps/alsijil/rules.py
index 015e47e59ade466adca78e065f1767beeb6bd5be..f48bf498db59bb86fc8c890cd1c802b46e698da6 100644
--- a/aleksis/apps/alsijil/rules.py
+++ b/aleksis/apps/alsijil/rules.py
@@ -22,6 +22,7 @@ from .util.predicates import (
     is_group_member,
     is_group_owner,
     is_group_role_assignment_group_owner,
+    is_in_allowed_time_range,
     is_lesson_event_group_owner,
     is_lesson_event_teacher,
     is_lesson_original_teacher,
@@ -400,8 +401,10 @@ add_perm(
     "alsijil.add_documentation_for_lesson_event_rule", add_documentation_for_lesson_event_predicate
 )
 
-edit_documentation_predicate = has_person & (
-    has_global_perm("alsijil.change_documentation") | can_edit_documentation
+edit_documentation_predicate = (
+    has_person
+    & (has_global_perm("alsijil.change_documentation") | can_edit_documentation)
+    & is_in_allowed_time_range
 )
 add_perm("alsijil.edit_documentation_rule", edit_documentation_predicate)
 add_perm("alsijil.delete_documentation_rule", edit_documentation_predicate)
diff --git a/aleksis/apps/alsijil/schema/__init__.py b/aleksis/apps/alsijil/schema/__init__.py
index afc8fd8d2c1835b69a9ace6c92d30312faaa665c..d762a9874e9ad85f98de86a2adff2fdd4af388fd 100644
--- a/aleksis/apps/alsijil/schema/__init__.py
+++ b/aleksis/apps/alsijil/schema/__init__.py
@@ -14,11 +14,7 @@ from aleksis.core.util.core_helpers import has_person
 
 from ..models import Documentation
 from .documentation import (
-    DocumentationBatchCreateMutation,
     DocumentationBatchCreateOrUpdateMutation,
-    DocumentationBatchPatchMutation,
-    DocumentationCreateMutation,
-    DocumentationDeleteMutation,
     DocumentationType,
 )
 
@@ -35,6 +31,7 @@ class Query(graphene.ObjectType):
         obj_id=graphene.ID(required=False),
         date_start=graphene.Date(required=True),
         date_end=graphene.Date(required=True),
+        incomplete=graphene.Boolean(required=False),
     )
 
     groups_by_person = FilterOrderList(GroupType, person=graphene.ID())
@@ -47,7 +44,15 @@ class Query(graphene.ObjectType):
         return documentations
 
     def resolve_documentations_for_coursebook(
-        root, info, own, date_start, date_end, obj_type=None, obj_id=None, **kwargs
+        root,
+        info,
+        own,
+        date_start,
+        date_end,
+        obj_type=None,
+        obj_id=None,
+        incomplete=False,
+        **kwargs,
     ):
         datetime_start = datetime.combine(date_start, datetime.min.time())
         datetime_end = datetime.combine(date_end, datetime.max.time())
@@ -75,7 +80,7 @@ class Query(graphene.ObjectType):
             raise PermissionDenied()
 
         return Documentation.get_for_coursebook(
-            own, datetime_start, datetime_end, info.context, obj_type, obj_id
+            own, datetime_start, datetime_end, info.context, obj_type, obj_id, incomplete
         )
 
     @staticmethod
@@ -113,9 +118,4 @@ class Query(graphene.ObjectType):
 
 
 class Mutation(graphene.ObjectType):
-    create_documentation = DocumentationCreateMutation.Field()
-    create_documentations = DocumentationBatchCreateMutation.Field()
-    delete_documentation = DocumentationDeleteMutation.Field()
-    update_documentations = DocumentationBatchPatchMutation.Field()
-
     create_or_update_documentations = DocumentationBatchCreateOrUpdateMutation.Field()
diff --git a/aleksis/apps/alsijil/schema/documentation.py b/aleksis/apps/alsijil/schema/documentation.py
index 43e958e910194be5a5b9adff9727c933093591b1..442f2156a92778861d5d5a7584e34a12ddae9767 100644
--- a/aleksis/apps/alsijil/schema/documentation.py
+++ b/aleksis/apps/alsijil/schema/documentation.py
@@ -1,26 +1,23 @@
-from datetime import datetime, timezone
+from datetime import datetime
 
 from django.core.exceptions import PermissionDenied
+from django.utils.timezone import localdate, localtime
 
 import graphene
 from graphene_django.types import DjangoObjectType
-from graphene_django_cud.mutations import (
-    DjangoBatchCreateMutation,
-    DjangoBatchPatchMutation,
-    DjangoCreateMutation,
-)
 from guardian.shortcuts import get_objects_for_user
+from reversion import create_revision, set_comment, set_user
 
+from aleksis.apps.alsijil.util.predicates import can_edit_documentation, is_in_allowed_time_range
 from aleksis.apps.chronos.models import LessonEvent
 from aleksis.apps.cursus.models import Subject
 from aleksis.apps.cursus.schema import CourseType, SubjectType
 from aleksis.core.models import Person
 from aleksis.core.schema.base import (
-    DeleteMutation,
     DjangoFilterMixin,
-    PermissionBatchPatchMixin,
     PermissionsTypeMixin,
 )
+from aleksis.core.util.core_helpers import get_site_preferences
 
 from ..models import Documentation
 
@@ -50,90 +47,28 @@ class DocumentationType(PermissionsTypeMixin, DjangoFilterMixin, DjangoObjectTyp
     course = graphene.Field(CourseType, required=False)
     subject = graphene.Field(SubjectType, required=False)
 
+    future_notice = graphene.Boolean(required=False)
+
+    old_id = graphene.ID(required=False)
+
     @staticmethod
     def resolve_teachers(root: Documentation, info, **kwargs):
         if not str(root.pk).startswith("DUMMY") and hasattr(root, "teachers"):
-            return  root.teachers
+            return root.teachers
         return root.lesson_event.teachers
 
+    @staticmethod
+    def resolve_future_notice(root: Documentation, info, **kwargs):
+        """Show whether the user can't edit the documentation because it's in the future."""
+        return not is_in_allowed_time_range(info.context.user, root) and can_edit_documentation(
+            info.context.user, root
+        )
+
     @classmethod
     def get_queryset(cls, queryset, info):
         return get_objects_for_user(info.context.user, "alsijil.view_documentation", queryset)
 
 
-class DocumentationCreateMutation(DjangoCreateMutation):
-    class Meta:
-        model = Documentation
-        only_fields = (
-            "course",
-            "lesson_event",
-            "subject",
-            "topic",
-            "homework",
-            "group_note",
-            "datetime_start",
-            "datetime_end",
-            "date_start",
-            "date_end",
-        )
-        optional_fields = (
-            "course",
-            "lesson_event",
-            "subject",
-            "topic",
-            "homework",
-            "group_note",
-            "datetime_start",
-            "datetime_end",
-            "date_start",
-            "date_end",
-        )
-        permissions = ("alsijil.add_documentation",)  # FIXME
-
-
-class DocumentationBatchCreateMutation(DjangoBatchCreateMutation):
-    class Meta:
-        model = Documentation
-        only_fields = (
-            "id",
-            "course",
-            "lesson_event",
-            "subject",
-            "topic",
-            "homework",
-            "group_note",
-            "datetime_start",
-            "datetime_end",
-            "date_start",
-            "date_end",
-        )
-        permissions = ("alsijil.add_documentation",)  # FIXME
-
-
-class DocumentationDeleteMutation(DeleteMutation):
-    klass = Documentation
-    permission_required = "alsijil.delete_documentation_rule"  # FIXME
-
-
-class DocumentationBatchPatchMutation(PermissionBatchPatchMixin, DjangoBatchPatchMutation):
-    class Meta:
-        model = Documentation
-        only_fields = (
-            "id",
-            "course",
-            "lesson_event",
-            "subject",
-            "topic",
-            "homework",
-            "group_note",
-            "datetime_start",
-            "datetime_end",
-            "date_start",
-            "date_end",
-        )
-        permissions = ("alsijil.edit_documentation_rule",)  # FIXME
-
-
 class DocumentationInputType(graphene.InputObjectType):
     id = graphene.ID(required=True)
     course = graphene.ID(required=False)
@@ -158,33 +93,48 @@ class DocumentationBatchCreateOrUpdateMutation(graphene.Mutation):
         # Sadly, we can't use the update_or_create method since create_defaults
         # is only introduced in Django 5.0
         if _id.startswith("DUMMY"):
-            dummy, lesson_event_id, datetime_start, datetime_end = _id.split(";")
+            dummy, lesson_event_id, datetime_start_iso, datetime_end_iso = _id.split(";")
             lesson_event = LessonEvent.objects.get(id=lesson_event_id)
 
-            if not info.context.user.has_perm(
+            datetime_start = datetime.fromisoformat(datetime_start_iso).astimezone(
+                lesson_event.timezone
+            )
+            datetime_end = datetime.fromisoformat(datetime_end_iso).astimezone(
+                lesson_event.timezone
+            )
+
+            if info.context.user.has_perm(
                 "alsijil.add_documentation_for_lesson_event_rule", lesson_event
+            ) and (
+                get_site_preferences()["alsijil__allow_edit_future_documentations"] == "all"
+                or (
+                    get_site_preferences()["alsijil__allow_edit_future_documentations"]
+                    == "current_day"
+                    and datetime_start.date() <= localdate()
+                )
+                or (
+                    get_site_preferences()["alsijil__allow_edit_future_documentations"]
+                    == "current_time"
+                    and datetime_start <= localtime()
+                )
             ):
-                raise PermissionDenied()
-
-            # Timezone removal is necessary due to ISO style offsets are no valid timezones.
-            # Instead, we take the timezone from the lesson_event and save it in a dedicated field.
-            obj = Documentation.objects.create(
-                datetime_start=datetime.fromisoformat(datetime_start).replace(tzinfo=timezone.utc),
-                datetime_end=datetime.fromisoformat(datetime_end).replace(tzinfo=timezone.utc),
-                timezone=lesson_event.timezone,
-                lesson_event=lesson_event,
-                course=lesson_event.course,
-                subject=lesson_event.subject,
-                topic=doc.topic or "",
-                homework=doc.homework or "",
-                group_note=doc.group_note or "",
-            )
-            if doc.teachers is not None:
-                obj.teachers.add(*doc.teachers)
-            else:
-                obj.teachers.set(lesson_event.teachers.all())
-            obj.save()
-            return obj
+                obj = Documentation.objects.create(
+                    datetime_start=datetime_start,
+                    datetime_end=datetime_end,
+                    lesson_event=lesson_event,
+                    course=lesson_event.course,
+                    subject=lesson_event.subject,
+                    topic=doc.topic or "",
+                    homework=doc.homework or "",
+                    group_note=doc.group_note or "",
+                )
+                if doc.teachers is not None:
+                    obj.teachers.add(*doc.teachers)
+                else:
+                    obj.teachers.set(lesson_event.teachers.all())
+                obj.save()
+                return obj
+            raise PermissionDenied()
         else:
             obj = Documentation.objects.get(id=_id)
 
@@ -208,6 +158,9 @@ class DocumentationBatchCreateOrUpdateMutation(graphene.Mutation):
 
     @classmethod
     def mutate(cls, root, info, input):  # noqa
-        objs = [cls.create_or_update(info, doc) for doc in input]
+        with create_revision():
+            set_user(info.context.user)
+            set_comment("Updated in coursebook")
+            objs = [cls.create_or_update(info, doc) for doc in input]
 
         return DocumentationBatchCreateOrUpdateMutation(documentations=objs)
diff --git a/aleksis/apps/alsijil/util/predicates.py b/aleksis/apps/alsijil/util/predicates.py
index e337043174025ffd81d4f9d2a426f87ec10c47b7..b1d9c45e5a22edf54e3e1eef8befcd8d0602ce69 100644
--- a/aleksis/apps/alsijil/util/predicates.py
+++ b/aleksis/apps/alsijil/util/predicates.py
@@ -1,12 +1,14 @@
 from typing import Any, Union
 
 from django.contrib.auth.models import User
+from django.utils.timezone import localdate, localtime
 
 from rules import predicate
 
 from aleksis.apps.chronos.models import Event, ExtraLesson, LessonEvent, LessonPeriod
 from aleksis.apps.cursus.models import Course
 from aleksis.core.models import Group, Person
+from aleksis.core.util.core_helpers import get_site_preferences
 from aleksis.core.util.predicates import check_object_permission
 
 from ..models import Documentation, PersonalNote
@@ -390,9 +392,7 @@ def can_view_documentation(user: User, obj: Documentation):
                 | is_lesson_event_group_owner(user, obj.lesson_event)
             )
         if obj.course:
-            return (
-                is_course_teacher(user, obj.course)
-            )
+            return is_course_teacher(user, obj.course)
     return False
 
 
@@ -407,3 +407,21 @@ def can_edit_documentation(user: User, obj: Documentation):
         if obj.course:
             return is_course_teacher(user, obj.course)
     return False
+
+
+@predicate
+def is_in_allowed_time_range(user: User, obj: Documentation):
+    """Predicate which checks if the documentation is in the allowed time range for editing."""
+    if obj and (
+        get_site_preferences()["alsijil__allow_edit_future_documentations"] == "all"
+        or (
+            get_site_preferences()["alsijil__allow_edit_future_documentations"] == "current_day"
+            and obj.datetime_start.date() <= localdate()
+        )
+        or (
+            get_site_preferences()["alsijil__allow_edit_future_documentations"] == "current_time"
+            and obj.datetime_start <= localtime()
+        )
+    ):
+        return True
+    return False