diff --git a/aleksis/apps/alsijil/forms.py b/aleksis/apps/alsijil/forms.py
index 228e825bae4e1170a3327629b6c8a973c192f7c0..d1d47cb0bb23259aed7695c81cd0ed4ca60d5190 100644
--- a/aleksis/apps/alsijil/forms.py
+++ b/aleksis/apps/alsijil/forms.py
@@ -1,8 +1,9 @@
-from datetime import datetime
+from datetime import datetime, timedelta
 
 from django import forms
 from django.core.exceptions import ValidationError
 from django.db.models import Count, Q
+from django.utils import timezone
 from django.utils.translation import gettext_lazy as _
 
 from django_select2.forms import ModelSelect2MultipleWidget, ModelSelect2Widget, Select2Widget
@@ -10,8 +11,8 @@ from guardian.shortcuts import get_objects_for_user
 from material import Fieldset, Layout, Row
 
 from aleksis.apps.chronos.managers import TimetableType
-from aleksis.apps.chronos.models import TimePeriod
-from aleksis.core.models import Group, Person
+from aleksis.apps.chronos.models import Subject, TimePeriod
+from aleksis.core.models import Group, Person, SchoolTerm
 from aleksis.core.util.core_helpers import get_site_preferences
 from aleksis.core.util.predicates import check_global_permission
 
@@ -254,3 +255,64 @@ class GroupRoleAssignmentEditForm(forms.ModelForm):
     class Meta:
         model = GroupRoleAssignment
         fields = ["date_start", "date_end"]
+
+
+class FilterRegisterObjectForm(forms.Form):
+    layout = Layout(
+        Row("school_term", "date_start", "date_end"), Row("has_documentation", "group", "subject")
+    )
+    school_term = forms.ModelChoiceField(queryset=None, label=_("School term"))
+    has_documentation = forms.NullBooleanField(label=_("Has lesson documentation"))
+    group = forms.ModelChoiceField(queryset=None, label=_("Group"), required=False)
+    subject = forms.ModelChoiceField(queryset=None, label=_("Subject"), required=False)
+    date_start = forms.DateField(label=_("Start date"))
+    date_end = forms.DateField(label=_("End date"))
+
+    @classmethod
+    def get_initial(cls):
+        date_end = timezone.now().date()
+        date_start = date_end - timedelta(days=30)
+        return {
+            "school_term": SchoolTerm.current,
+            "date_start": date_start,
+            "date_end": date_end,
+        }
+
+    def __init__(self, request, for_person=True, *args, **kwargs):
+        self.request = request
+        person = self.request.user.person
+
+        # Fill in initial data
+        kwargs["initial"] = self.get_initial()
+        super().__init__(*args, **kwargs)
+
+        # Build querysets
+        self.fields["school_term"].queryset = SchoolTerm.objects.all()
+
+        # Filter selectable groups by permissions
+        group_qs = Group.objects.all()
+        if for_person:
+            group_qs = group_qs.filter(
+                Q(lessons__teachers=person)
+                | Q(lessons__lesson_periods__substitutions__teachers=person)
+                | Q(events__teachers=person)
+                | Q(extra_lessons__teachers=person)
+            )
+        elif not check_global_permission(self.request.user, "alsijil.view_full_register"):
+            group_qs = group_qs.union(
+                group_qs.filter(
+                    pk__in=get_objects_for_user(
+                        self.request.user, "core.view_full_register_group", Group
+                    ).values_list("pk", flat=True)
+                )
+            )
+
+        # Flatten query by filtering groups by pk
+        groups_flat = Group.objects.filter(pk__in=list(group_qs.values_list("pk", flat=True)))
+        self.fields["group"].queryset = groups_flat
+
+        # Filter subjects by selectable groups
+        subject_qs = Subject.objects.filter(
+            Q(lessons__groups__in=groups_flat) | Q(extra_lessons__groups__in=groups_flat)
+        ).distinct()
+        self.fields["subject"].queryset = subject_qs
diff --git a/aleksis/apps/alsijil/tables.py b/aleksis/apps/alsijil/tables.py
index c3835e973164f5a6cd3b50540fa7b9f5a3d4ea43..d78f899ba9cd964cad239616b52a55fde447df47 100644
--- a/aleksis/apps/alsijil/tables.py
+++ b/aleksis/apps/alsijil/tables.py
@@ -78,3 +78,28 @@ class GroupRoleTable(tables.Table):
             self.columns.hide("edit")
         if not request.user.has_perm("alsijil.delete_grouprole"):
             self.columns.hide("delete")
+
+
+class RegisterObjectTable(tables.Table):
+    class Meta:
+        attrs = {"class": "highlight responsive-table"}
+
+    status = tables.Column(accessor="register_object")
+    date = tables.Column(order_by="date_sort")
+    period = tables.Column(order_by="period_sort")
+    groups = tables.Column()
+    subject = tables.Column()
+    topic = tables.Column()
+    homework = tables.Column()
+    group_note = tables.Column()
+
+    def render_status(self, value, record):
+        return render_to_string(
+            "alsijil/partials/lesson_status_icon.html",
+            dict(
+                week=record.get("week"),
+                has_documentation=record.get("has_documentation", False),
+                substitution=record.get("substitution"),
+                register_object=value,
+            ),
+        )
diff --git a/aleksis/apps/alsijil/templates/alsijil/class_register/person.html b/aleksis/apps/alsijil/templates/alsijil/class_register/person.html
index 7254e0e24be0f979f9fa1f01a05a0b4c8f10e532..5302e24890221a731940bbe7999675bbc92bf143 100644
--- a/aleksis/apps/alsijil/templates/alsijil/class_register/person.html
+++ b/aleksis/apps/alsijil/templates/alsijil/class_register/person.html
@@ -1,9 +1,6 @@
 {# -*- engine:django -*- #}
 {% extends "core/base.html" %}
-{% load rules %}
-{% load data_helpers %}
-{% load week_helpers %}
-{% load i18n %}
+{% load rules data_helpers week_helpers i18n material_form django_tables2 %}
 
 {% block browser_title %}{% blocktrans %}Class register: person{% endblocktrans %}{% endblock %}
 
@@ -22,249 +19,280 @@
 {% endblock %}
 
 {% block content %}
-  {% has_perm "alsijil.edit_person_overview_personalnote" user person as can_mark_all_as_excused %}
-  {% has_perm "alsijil.register_absence" user person as can_register_absence %}
-  {% if can_register_absence %}
-    <a class="btn primary-color waves-effect waves-light" href="{% url "register_absence" person.pk %}">
-      <i class="material-icons left">rate_review</i>
-      {% trans "Register absence" %}
-    </a>
-  {% endif %}
-
   <div class="row">
-  <div class="col s12 m12 l6">
-    <h5>{% trans "Unexcused absences" %}</h5>
-
-    <ul class="collection">
-      {% for note in unexcused_absences %}
-        <li class="collection-item">
-          {% has_perm "alsijil.edit_personalnote" user note as can_edit_personal_note %}
-          {% if can_edit_personal_note %}
-            <form action="" method="post" class="right hide-on-small-only" style="margin-top: -7px;">
-              {% csrf_token %}
-              {% trans "Mark as" %}
-              <input type="hidden" value="{{ note.pk }}" name="personal_note">
-              {% include "alsijil/partials/mark_as_buttons.html" %}
-              <a class="btn-flat red-text" title="{% trans "Delete note" %}"
-                 href="{% url "delete_personal_note" note.pk %}">
-                <i class="material-icons center">cancel</i>
-              </a>
-            </form>
-          {% endif %}
-          <i class="material-icons left red-text">warning</i>
-          <p class="no-margin">
-            <a href="{{ note.get_absolute_url }}">{{ note.date }}, {{ note.lesson_period }}</a>
-          </p>
-          {% if note.remarks %}
-            <p class="no-margin"><em>{{ note.remarks }}</em></p>
-          {% endif %}
-          {% if can_edit_personal_note %}
-            <form action="" method="post" class="hide-on-med-and-up">
-              {% csrf_token %}
-              {% trans "Mark as" %}
-              <input type="hidden" value="{{ note.pk }}" name="personal_note">
-              {% include "alsijil/partials/mark_as_buttons.html" %}
-              <a class="btn-flat red-text" title="{% trans "Delete note" %}"
-                 href="{% url "delete_personal_note" note.pk %}">
-                <i class="material-icons center">cancel</i>
-              </a>
-            </form>
-          {% endif %}
-        </li>
-      {% empty %}
-        <li class="collection-item avatar valign-wrapper">
-          <i class="material-icons left materialize-circle green white-text">check</i>
-          <span class="title">{% trans "There are no unexcused lessons." %}</span>
+  <div class="col s12">
+    <ul class="tabs">
+      {% if register_object_table %}
+        <li class="tab">
+          <a href="#lesson-documentations">{% trans "Lesson documentations" %}</a>
         </li>
-      {% endfor %}
+      {% endif %}
+      <li class="tab">
+        <a href="#personal-notes">{% trans "Personal notes" %}</a>
+      </li>
     </ul>
-    {% if stats %}
-      <h5>{% trans "Statistics on absences, tardiness and remarks" %}</h5>
-      <ul class="collapsible">
-        {% for school_term, stat in stats %}
-          <li {% if forloop.first %}class="active"{% endif %}>
-            <div class="collapsible-header">
-              <i class="material-icons">date_range</i>{{ school_term }}</div>
-            <div class="collapsible-body">
-              <table>
-                <tr>
-                  <th colspan="2">{% trans 'Absences' %}</th>
-                  <td>{{ stat.absences_count }}</td>
-                </tr>
-                <tr>
-                  <td rowspan="{{ excuse_types.count|add:2 }}" class="hide-on-small-only">{% trans "thereof" %}</td>
-                  <td rowspan="{{ excuse_types.count|add:2 }}" class="hide-on-med-and-up"></td>
-                  <th class="truncate">{% trans 'Excused' %}</th>
-                  <td>{{ stat.excused }}</td>
-                </tr>
-                {% for excuse_type in excuse_types %}
-                  <th>{{ excuse_type.name }}</th>
-                  <td>{{ stat|get_dict:excuse_type.count_label }}</td>
-                {% endfor %}
-                <tr>
-                  <th>{% trans 'Unexcused' %}</th>
-                  <td>{{ stat.unexcused }}</td>
-                </tr>
-                <tr>
-                  <th colspan="2">{% trans 'Tardiness' %}</th>
-                  <td>{{ stat.tardiness }}'/{{ stat.tardiness_count }} &times;</td>
-                </tr>
-                {% for extra_mark in extra_marks %}
-                  <tr>
-                    <th colspan="2">{{ extra_mark.name }}</th>
-                    <td>{{ stat|get_dict:extra_mark.count_label }}</td>
-                  </tr>
-                {% endfor %}
-              </table>
-            </div>
-          </li>
-        {% endfor %}
-      </ul>
-    {% endif %}
   </div>
-  <div class="col s12 m12 l6">
-    <h5>{% trans "Relevant personal notes" %}</h5>
-    <ul class="collapsible">
-      <li>
-        <div>
-          <ul>
-            {% for note in personal_notes %}
-              {% ifchanged note.school_term %}</ul></div></li>
-                <li {% if forloop.first %}class="active"{% endif %}>
-                <div class="collapsible-header"><i
-                    class="material-icons">date_range</i>{{ note.school_term }}</div>
-                <div class="collapsible-body">
-                <ul class="collection">
-              {% endifchanged %}
-
-              {% ifchanged note.week %}
-                <li class="collection-item">
-                  <strong>{% blocktrans with week=note.calendar_week.week %}Week {{ week }}{% endblocktrans %}</strong>
-                </li>
-              {% endifchanged %}
-              {% ifchanged note.date %}
-                <li class="collection-item">
-                  {% if can_mark_all_as_excused and note.date %}
-                    <form action="" method="post" class="right hide-on-small-only" style="margin-top: -7px;">
-                      {% csrf_token %}
-                      {% trans "Mark all as" %}
-                      <input type="hidden" value="{{ note.date|date:"Y-m-d" }}" name="date">
-                      {% include "alsijil/partials/mark_as_buttons.html" %}
-                    </form>
-                  {% endif %}
-                  <i class="material-icons left">schedule</i>
+  {% if register_object_table %}
+    <div class="col s12" id="lesson-documentations">
+      <h5>{% trans "Lesson filter" %}</h5>
+      <form action="" method="get">
+        {% form form=filter_form %}{% endform %}
+        <button type="submit" class="btn waves-effect waves-light">
+          <i class="material-icons left">refresh</i>
+          {% trans "Update filters" %}
+        </button>
+      </form>
+      <h5>{% trans "Lesson table" %}</h5>
+      {% render_table register_object_table %}
+    </div>
+  {% endif %}
+  <div class="col s12" id="personal-notes">
+    {% has_perm "alsijil.edit_person_overview_personalnote" user person as can_mark_all_as_excused %}
+    {% has_perm "alsijil.register_absence" user person as can_register_absence %}
+    {% if can_register_absence %}
+      <a class="btn primary-color waves-effect waves-light" href="{% url "register_absence" person.pk %}">
+        <i class="material-icons left">rate_review</i>
+        {% trans "Register absence" %}
+      </a>
+    {% endif %}
 
-                  {% if note.date %}
-                    {{ note.date }}
-                  {% else %}
-                    {{ note.register_object.date_start }}
-                    {{ note.register_object.period_from.period }}.–{{ note.register_object.date_end }}
-                    {{ note.register_object.period_to.period }}.
-                  {% endif %}
+    <div class="row">
+      <div class="col s12 m12 l6">
+        <h5>{% trans "Unexcused absences" %}</h5>
 
-                  {% if can_mark_all_as_excused and note.date %}
-                    <form action="" method="post" class="hide-on-med-and-up">
-                      {% csrf_token %}
-                      {% trans "Mark all as" %}
-                      <input type="hidden" value="{{ note.date|date:"Y-m-d" }}" name="date">
-                      {% include "alsijil/partials/mark_as_buttons.html" %}
-                    </form>
-                  {% endif %}
-                </li>
-              {% endifchanged %}
+        <ul class="collection">
+          {% for note in unexcused_absences %}
+            <li class="collection-item">
+              {% has_perm "alsijil.edit_personalnote" user note as can_edit_personal_note %}
+              {% if can_edit_personal_note %}
+                <form action="" method="post" class="right hide-on-small-only" style="margin-top: -7px;">
+                  {% csrf_token %}
+                  {% trans "Mark as" %}
+                  <input type="hidden" value="{{ note.pk }}" name="personal_note">
+                  {% include "alsijil/partials/mark_as_buttons.html" %}
+                  <a class="btn-flat red-text" title="{% trans "Delete note" %}"
+                     href="{% url "delete_personal_note" note.pk %}">
+                    <i class="material-icons center">cancel</i>
+                  </a>
+                </form>
+              {% endif %}
+              <i class="material-icons left red-text">warning</i>
+              <p class="no-margin">
+                <a href="{{ note.get_absolute_url }}">{{ note.date }}, {{ note.lesson_period }}</a>
+              </p>
+              {% if note.remarks %}
+                <p class="no-margin"><em>{{ note.remarks }}</em></p>
+              {% endif %}
+              {% if can_edit_personal_note %}
+                <form action="" method="post" class="hide-on-med-and-up">
+                  {% csrf_token %}
+                  {% trans "Mark as" %}
+                  <input type="hidden" value="{{ note.pk }}" name="personal_note">
+                  {% include "alsijil/partials/mark_as_buttons.html" %}
+                  <a class="btn-flat red-text" title="{% trans "Delete note" %}"
+                     href="{% url "delete_personal_note" note.pk %}">
+                    <i class="material-icons center">cancel</i>
+                  </a>
+                </form>
+              {% endif %}
+            </li>
+            {% empty %}
+            <li class="collection-item avatar valign-wrapper">
+              <i class="material-icons left materialize-circle green white-text">check</i>
+              <span class="title">{% trans "There are no unexcused lessons." %}</span>
+            </li>
+          {% endfor %}
+        </ul>
+        {% if stats %}
+          <h5>{% trans "Statistics on absences, tardiness and remarks" %}</h5>
+          <ul class="collapsible">
+            {% for school_term, stat in stats %}
+              <li {% if forloop.first %}class="active"{% endif %}>
+                <div class="collapsible-header">
+                  <i class="material-icons">date_range</i>{{ school_term }}</div>
+                <div class="collapsible-body">
+                  <table>
+                    <tr>
+                      <th colspan="2">{% trans 'Absences' %}</th>
+                      <td>{{ stat.absences_count }}</td>
+                    </tr>
+                    <tr>
+                      <td rowspan="{{ excuse_types.count|add:2 }}" class="hide-on-small-only">{% trans "thereof" %}</td>
+                      <td rowspan="{{ excuse_types.count|add:2 }}" class="hide-on-med-and-up"></td>
+                      <th class="truncate">{% trans 'Excused' %}</th>
+                      <td>{{ stat.excused }}</td>
+                    </tr>
+                    {% for excuse_type in excuse_types %}
+                      <th>{{ excuse_type.name }}</th>
+                      <td>{{ stat|get_dict:excuse_type.count_label }}</td>
+                    {% endfor %}
+                    <tr>
+                      <th>{% trans 'Unexcused' %}</th>
+                      <td>{{ stat.unexcused }}</td>
+                    </tr>
+                    <tr>
+                      <th colspan="2">{% trans 'Tardiness' %}</th>
+                      <td>{{ stat.tardiness }}'/{{ stat.tardiness_count }} &times;</td>
+                    </tr>
+                    {% for extra_mark in extra_marks %}
+                      <tr>
+                        <th colspan="2">{{ extra_mark.name }}</th>
+                        <td>{{ stat|get_dict:extra_mark.count_label }}</td>
+                      </tr>
+                    {% endfor %}
+                  </table>
+                </div>
+              </li>
+            {% endfor %}
+          </ul>
+        {% endif %}
+      </div>
+      <div class="col s12 m12 l6">
+        <h5>{% trans "Relevant personal notes" %}</h5>
+        <ul class="collapsible">
+          <li>
+            <div>
+              <ul>
+                {% for note in personal_notes %}
+                  {% ifchanged note.school_term %}</ul></div></li>
+                    <li {% if forloop.first %}class="active"{% endif %}>
+                    <div class="collapsible-header"><i
+                        class="material-icons">date_range</i>{{ note.school_term }}</div>
+                    <div class="collapsible-body">
+                    <ul class="collection">
+                  {% endifchanged %}
 
-              <li class="collection-item">
-                <div class="row no-margin">
-                  <div class="col s2 m1">
-                    {% if note.register_object.period %}
-                      {{ note.register_object.period.period }}.
-                    {% endif %}
-                  </div>
+                  {% ifchanged note.week %}
+                    <li class="collection-item">
+                      <strong>{% blocktrans with week=note.calendar_week.week %}Week
+                        {{ week }}{% endblocktrans %}</strong>
+                    </li>
+                  {% endifchanged %}
+                  {% ifchanged note.date %}
+                    <li class="collection-item">
+                      {% if can_mark_all_as_excused and note.date %}
+                        <form action="" method="post" class="right hide-on-small-only" style="margin-top: -7px;">
+                          {% csrf_token %}
+                          {% trans "Mark all as" %}
+                          <input type="hidden" value="{{ note.date|date:"Y-m-d" }}" name="date">
+                          {% include "alsijil/partials/mark_as_buttons.html" %}
+                        </form>
+                      {% endif %}
+                      <i class="material-icons left">schedule</i>
 
-                  <div class="col s10 m4">
-                    <i class="material-icons left">event_note</i>
-                    <a href="{{ note.get_absolute_url }}">
-                      {% if note.register_object.get_subject %}
-                        {{ note.register_object.get_subject.name }}
+                      {% if note.date %}
+                        {{ note.date }}
                       {% else %}
-                        {% trans "Event" %} ({{ note.register_object.title }})
-                      {% endif %}<br/>
-                      {{ note.register_object.teacher_names }}
-                    </a>
-                  </div>
+                        {{ note.register_object.date_start }}
+                        {{ note.register_object.period_from.period }}.–{{ note.register_object.date_end }}
+                        {{ note.register_object.period_to.period }}.
+                      {% endif %}
 
-                  <div class="col s12 m7 no-padding">
-                    {% has_perm "alsijil.edit_personalnote" user note as can_edit_personal_note %}
-                    {% if note.absent and not note.excused and can_edit_personal_note %}
-                      <form action="" method="post" class="right hide-on-small-only" style="margin-top: -7px;">
-                        {% csrf_token %}
-                        {% trans "Mark as" %}
-                        <input type="hidden" value="{{ note.pk }}" name="personal_note">
-                        {% include "alsijil/partials/mark_as_buttons.html" %}
-                        <a class="btn-flat red-text" title="{% trans "Delete note" %}"
-                           href="{% url "delete_personal_note" note.pk %}">
-                          <i class="material-icons center">cancel</i>
-                        </a>
-                      </form>
-                    {% elif can_edit_personal_note %}
-                      <a class="btn-flat red-text right hide-on-small-only" title="{% trans "Delete note" %}"
-                         href="{% url "delete_personal_note" note.pk %}">
-                        <i class="material-icons center">cancel</i>
-                      </a>
-                    {% endif %}
+                      {% if can_mark_all_as_excused and note.date %}
+                        <form action="" method="post" class="hide-on-med-and-up">
+                          {% csrf_token %}
+                          {% trans "Mark all as" %}
+                          <input type="hidden" value="{{ note.date|date:"Y-m-d" }}" name="date">
+                          {% include "alsijil/partials/mark_as_buttons.html" %}
+                        </form>
+                      {% endif %}
+                    </li>
+                  {% endifchanged %}
 
-                    {% if note.absent %}
-                      <div class="chip red white-text">
-                        {% trans 'Absent' %}
-                      </div>
-                    {% endif %}
-                    {% if note.excused %}
-                      <div class="chip green white-text">
-                        {% if note.excuse_type %}
-                          {{ note.excuse_type.name }}
-                        {% else %}
-                          {% trans 'Excused' %}
+                  <li class="collection-item">
+                    <div class="row no-margin">
+                      <div class="col s2 m1">
+                        {% if note.register_object.period %}
+                          {{ note.register_object.period.period }}.
                         {% endif %}
                       </div>
-                    {% endif %}
 
-                    {% if note.late %}
-                      <div class="chip orange white-text">
-                        {% blocktrans with late=note.late %}{{ late }}' late{% endblocktrans %}
+                      <div class="col s10 m4">
+                        <i class="material-icons left">event_note</i>
+                        <a href="{{ note.get_absolute_url }}">
+                          {% if note.register_object.get_subject %}
+                            {{ note.register_object.get_subject.name }}
+                          {% else %}
+                            {% trans "Event" %} ({{ note.register_object.title }})
+                          {% endif %}<br/>
+                          {{ note.register_object.teacher_names }}
+                        </a>
                       </div>
-                    {% endif %}
 
-                    {% for extra_mark in note.extra_marks.all %}
-                      <div class="chip">{{ extra_mark.name }}</div>
-                    {% endfor %}
+                      <div class="col s12 m7 no-padding">
+                        {% has_perm "alsijil.edit_personalnote" user note as can_edit_personal_note %}
+                        {% if note.absent and not note.excused and can_edit_personal_note %}
+                          <form action="" method="post" class="right hide-on-small-only" style="margin-top: -7px;">
+                            {% csrf_token %}
+                            {% trans "Mark as" %}
+                            <input type="hidden" value="{{ note.pk }}" name="personal_note">
+                            {% include "alsijil/partials/mark_as_buttons.html" %}
+                            <a class="btn-flat red-text" title="{% trans "Delete note" %}"
+                               href="{% url "delete_personal_note" note.pk %}">
+                              <i class="material-icons center">cancel</i>
+                            </a>
+                          </form>
+                          {% elif can_edit_personal_note %}
+                          <a class="btn-flat red-text right hide-on-small-only" title="{% trans "Delete note" %}"
+                             href="{% url "delete_personal_note" note.pk %}">
+                            <i class="material-icons center">cancel</i>
+                          </a>
+                        {% endif %}
 
-                    <em>{{ note.remarks }}</em>
+                        {% if note.absent %}
+                          <div class="chip red white-text">
+                            {% trans 'Absent' %}
+                          </div>
+                        {% endif %}
+                        {% if note.excused %}
+                          <div class="chip green white-text">
+                            {% if note.excuse_type %}
+                              {{ note.excuse_type.name }}
+                            {% else %}
+                              {% trans 'Excused' %}
+                            {% endif %}
+                          </div>
+                        {% endif %}
 
-                  </div>
-                  <div class="col s12 hide-on-med-and-up">
-                    {% if note.absent and not note.excused and can_edit_personal_note %}
-                      <form action="" method="post">
-                        {% csrf_token %}
-                        {% trans "Mark as" %}
-                        <input type="hidden" value="{{ note.pk }}" name="personal_note">
-                        {% include "alsijil/partials/mark_as_buttons.html" %}
-                        <a class="btn-flat red-text" title="{% trans "Delete note" %}"
-                           href="{% url "delete_personal_note" note.pk %}">
-                          <i class="material-icons center">cancel</i>
-                        </a>
-                      </form>
-                    {% elif can_edit_personal_note %}
-                      <a class="btn-flat red-text" title="{% trans "Delete note" %}"
-                         href="{% url "delete_personal_note" note.pk %}">
-                        <i class="material-icons left">cancel</i>
-                        {% trans "Delete" %}
-                      </a>
-                    {% endif %}
-                  </div>
-              </li>
-            {% endfor %}
-            </li>
-            </ul>
-            </div>
+                        {% if note.late %}
+                          <div class="chip orange white-text">
+                            {% blocktrans with late=note.late %}{{ late }}' late{% endblocktrans %}
+                          </div>
+                        {% endif %}
+
+                        {% for extra_mark in note.extra_marks.all %}
+                          <div class="chip">{{ extra_mark.name }}</div>
+                        {% endfor %}
+
+                        <em>{{ note.remarks }}</em>
+
+                      </div>
+                      <div class="col s12 hide-on-med-and-up">
+                        {% if note.absent and not note.excused and can_edit_personal_note %}
+                          <form action="" method="post">
+                            {% csrf_token %}
+                            {% trans "Mark as" %}
+                            <input type="hidden" value="{{ note.pk }}" name="personal_note">
+                            {% include "alsijil/partials/mark_as_buttons.html" %}
+                            <a class="btn-flat red-text" title="{% trans "Delete note" %}"
+                               href="{% url "delete_personal_note" note.pk %}">
+                              <i class="material-icons center">cancel</i>
+                            </a>
+                          </form>
+                          {% elif can_edit_personal_note %}
+                          <a class="btn-flat red-text" title="{% trans "Delete note" %}"
+                             href="{% url "delete_personal_note" note.pk %}">
+                            <i class="material-icons left">cancel</i>
+                            {% trans "Delete" %}
+                          </a>
+                        {% endif %}
+                      </div>
+                  </li>
+                {% endfor %}
+                </li>
+                </ul>
+                </div>
+      </div>
+    </div>
   </div>
 {% endblock %}
diff --git a/aleksis/apps/alsijil/templates/alsijil/partials/lesson_status_icon.html b/aleksis/apps/alsijil/templates/alsijil/partials/lesson_status_icon.html
index ceff8c1a17ea0452b1f013d759dbee7edfd6c630..52f55e9723c3b9650bcd09f63725467a75f8993d 100644
--- a/aleksis/apps/alsijil/templates/alsijil/partials/lesson_status_icon.html
+++ b/aleksis/apps/alsijil/templates/alsijil/partials/lesson_status_icon.html
@@ -2,7 +2,7 @@
 
 {% now_datetime as now_dt %}
 
-{% if register_object.has_documentation %}
+{% if has_documentation or register_object.has_documentation %}
   <i class="material-icons green{% firstof color_suffix "-text"%} tooltipped {{ css_class }}" data-position="bottom" data-tooltip="{% trans "Data complete" %}" title="{% trans "Data complete" %}">check_circle</i>
 {% elif not register_object.period %}
   {% period_to_time_start week register_object.raw_period_from_on_day as time_start %}
@@ -19,13 +19,13 @@
   {% period_to_time_start week register_object.period as time_start %}
   {% period_to_time_end week register_object.period as time_end %}
 
-  {% if register_object.get_substitution.cancelled %}
+  {% if substitution.cancelled or register_object.get_substitution.cancelled %}
     <i class="material-icons red{% firstof color_suffix "-text"%} tooltipped {{ css_class }}"  data-position="bottom" data-tooltip="{% trans "Lesson cancelled" %}" title="{% trans "Lesson cancelled" %}">cancel</i>
   {% elif now_dt > time_end %}
     <i class="material-icons red{% firstof color_suffix "-text"%} tooltipped {{ css_class }}"  data-position="bottom" data-tooltip="{% trans "Missing data" %}" title="{% trans "Missing data" %}">history</i>
   {% elif now_dt > time_start and now_dt < time_end %}
     <i class="material-icons orange{% firstof color_suffix "-text"%} tooltipped {{ css_class }}"  data-position="bottom" data-tooltip="{% trans "Pending" %}" title="{% trans "Pending" %}">more_horiz</i>
-  {% elif register_object.get_substitution %}
+  {% elif substitution or register_object.get_substitution %}
     <i class="material-icons orange{% firstof color_suffix "-text"%} tooltipped {{ css_class }}"  data-position="bottom" data-tooltip="{% trans "Substitution" %}" title="{% trans "Substitution" %}">update</i>
   {% endif %}
 {% endif %}
diff --git a/aleksis/apps/alsijil/util/alsijil_helpers.py b/aleksis/apps/alsijil/util/alsijil_helpers.py
index 9aedcb24ea2c8dd2c529d273b701b082281786ea..3d30d2395a017c4338a96ee5625f235d9c1aea30 100644
--- a/aleksis/apps/alsijil/util/alsijil_helpers.py
+++ b/aleksis/apps/alsijil/util/alsijil_helpers.py
@@ -1,15 +1,26 @@
+from operator import itemgetter
 from typing import List, Optional, Union
 
 from django.db.models.expressions import Exists, OuterRef
 from django.db.models.query import Prefetch, QuerySet
 from django.db.models.query_utils import Q
 from django.http import HttpRequest
+from django.utils.formats import date_format
+from django.utils.translation import gettext as _
 
 from calendarweek import CalendarWeek
 
+from aleksis.apps.alsijil.forms import FilterRegisterObjectForm
 from aleksis.apps.alsijil.models import LessonDocumentation
-from aleksis.apps.chronos.models import Event, ExtraLesson, LessonPeriod
+from aleksis.apps.chronos.models import (
+    Event,
+    ExtraLesson,
+    Holiday,
+    LessonPeriod,
+    LessonSubstitution,
+)
 from aleksis.apps.chronos.util.chronos_helpers import get_el_by_pk
+from aleksis.core.models import SchoolTerm
 
 
 def get_register_object_by_pk(
@@ -99,3 +110,216 @@ def register_objects_sorter(register_object: Union[LessonPeriod, Event, ExtraLes
         return register_object.period_from_on_day
     else:
         return 0
+
+
+def generate_list_of_all_register_objects(filter_dict: dict) -> List[dict]:
+    # Get data for filtering
+    initial_filter_data = FilterRegisterObjectForm.get_initial()
+    # Always force a selected school term so that queries won't get to big
+    filter_school_term = filter_dict.get("school_term", SchoolTerm.current)
+    filter_person = filter_dict.get("person")
+    should_have_documentation = filter_dict.get("has_documentation")
+    filter_group = filter_dict.get("group")
+    filter_subject = filter_dict.get("subject")
+    filter_date_start = filter_dict.get("date_start", initial_filter_data.get("date_start"))
+    filter_date_end = filter_dict.get("date_end", initial_filter_data.get("date_end"))
+    filter_date = filter_date_start and filter_date_end
+
+    # Get all holidays in the selected school term to sort all data in holidays out
+    holidays = Holiday.objects.within_dates(
+        filter_school_term.date_start, filter_school_term.date_end
+    )
+    event_q = Q()
+    extra_lesson_q = Q()
+    holiday_days = []
+    for holiday in holidays:
+        event_q = event_q | Q(date_start__lte=holiday.date_end, date_end__gte=holiday.date_start)
+        extra_lesson_q = extra_lesson_q | Q(day__gte=holiday.date_start, day__lte=holiday.date_end)
+        holiday_days += list(holiday.get_days())
+
+    lesson_periods = (
+        LessonPeriod.objects.select_related("lesson")
+        .prefetch_related("lesson__teachers", "lesson__groups")
+        .filter(lesson__validity__school_term=filter_school_term)
+        .distinct()
+        .order_by("lesson__validity__school_term__date_start")
+    )
+    events = Event.objects.filter(school_term=filter_school_term).exclude(event_q).distinct()
+    extra_lessons = (
+        ExtraLesson.objects.annotate_day()
+        .filter(school_term=filter_school_term)
+        .exclude(extra_lesson_q)
+        .distinct()
+    )
+
+    # Do filtering by date, by person, by group and by subject (if activated)
+    if filter_date:
+        events = events.within_dates(filter_date_start, filter_date_end)
+        extra_lessons = extra_lessons.filter(day__gte=filter_date_start, day__lte=filter_date_end)
+    if filter_person:
+        lesson_periods = lesson_periods.filter(
+            Q(lesson__teachers=filter_person) | Q(substitutions__teachers=filter_person)
+        )
+        events = events.filter_teacher(filter_person)
+        extra_lessons = extra_lessons.filter_teacher(filter_person)
+    if filter_group:
+        lesson_periods = lesson_periods.filter_group(filter_group)
+        events = events.filter_group(filter_group)
+        extra_lessons = extra_lessons.filter_group(filter_group)
+    if filter_subject:
+        lesson_periods = lesson_periods.filter(
+            Q(lesson__subject=filter_subject) | Q(substitutions__subject=filter_subject)
+        )
+        # As events have no subject, we exclude them at all
+        events = []
+        extra_lessons = extra_lessons.filter(subject=filter_subject)
+
+    # Prefetch documentations for all register objects and substitutions for all lesson periods
+    # in order to prevent extra queries
+    documentations = LessonDocumentation.objects.not_empty().filter(
+        Q(event__in=events)
+        | Q(extra_lesson__in=extra_lessons)
+        | Q(lesson_period__in=lesson_periods)
+    )
+    substitutions = LessonSubstitution.objects.filter(lesson_period__in=lesson_periods)
+    if filter_person:
+        substitutions = substitutions.filter(teachers=filter_person)
+
+    if lesson_periods:
+        # Get date range for which lesson periods should be added
+        date_start = lesson_periods.first().lesson.validity.school_term.date_start
+        date_end = lesson_periods.last().lesson.validity.school_term.date_end
+        if filter_date and filter_date_start > date_start and filter_date_start < date_end:
+            date_start = filter_date_start
+        if filter_date and filter_date_end < date_end and filter_date_start > date_start:
+            date_end = filter_date_end
+        print(date_start, date_end)
+        weeks = CalendarWeek.weeks_within(date_start, date_end)
+
+        register_objects = []
+        for lesson_period in lesson_periods:
+            for week in weeks:
+                day = week[lesson_period.period.weekday]
+
+                # Skip all lesson periods in holidays
+                if day in holiday_days:
+                    continue
+
+                # Ensure that the lesson period is in filter range and validity range
+                if (
+                    lesson_period.lesson.validity.date_start
+                    <= day
+                    <= lesson_period.lesson.validity.date_end
+                ) and (not filter_date or (filter_date_start <= day <= filter_date_end)):
+
+                    filtered_substitutions = list(
+                        filter(lambda s: s.lesson_period_id == lesson_period.id, substitutions)
+                    )
+                    # Skip lesson period if the person isn't a teacher
+                    # or substitution teacher of this lesson period
+                    if filter_person and (
+                        filter_person not in lesson_period.lesson.teachers.all()
+                        and not filtered_substitutions
+                    ):
+                        continue
+
+                    # Annotate substitution to lesson period
+                    sub = filtered_substitutions[0] if filtered_substitutions else None
+
+                    subject = sub.subject if sub and sub.subject else lesson_period.lesson.subject
+
+                    if filter_subject and filter_subject != subject:
+                        continue
+
+                    # Filter matching documentations and annotate if they exist
+                    filtered_documentations = list(
+                        filter(
+                            lambda d: d.week == week.week
+                            and d.year == week.year
+                            and d.lesson_period_id == lesson_period.pk,
+                            documentations,
+                        )
+                    )
+                    has_documentation = bool(filtered_documentations)
+
+                    if (
+                        should_have_documentation is not None
+                        and has_documentation != should_have_documentation
+                    ):
+                        continue
+
+                    # Build table entry
+                    entry = {
+                        "week": week,
+                        "has_documentation": has_documentation,
+                        "substitution": sub,
+                        "register_object": lesson_period,
+                        "date": date_format(day),
+                        "date_sort": day,
+                        "period": f"{lesson_period.period.period}.",
+                        "period_sort": lesson_period.period.period,
+                        "groups": lesson_period.lesson.group_names,
+                        "subject": subject.name,
+                    }
+                    if has_documentation:
+                        doc = filtered_documentations[0]
+                        entry["topic"] = doc.topic
+                        entry["homework"] = doc.homework
+                        entry["group_note"] = doc.group_note
+                    register_objects.append(entry)
+
+        for register_object in list(extra_lessons) + list(events):
+            filtered_documentations = list(
+                filter(
+                    lambda d: getattr(d, f"{register_object.label_}_id") == register_object.pk,
+                    documentations,
+                )
+            )
+            has_documentation = bool(filtered_documentations)
+
+            if (
+                should_have_documentation is not None
+                and has_documentation != should_have_documentation
+            ):
+                continue
+
+            if isinstance(register_object, ExtraLesson):
+                day = date_format(register_object.day)
+                day_sort = register_object.day
+                period = f"{register_object.period.period}."
+                period_sort = register_object.period.period
+            else:
+                day = (
+                    f"{date_format(register_object.date_start)}"
+                    f"–{date_format(register_object.date_end)}"
+                )
+                day_sort = (register_object.date_start,)
+                period = (
+                    f"{register_object.period_from.period}.–{register_object.period_to.period}."
+                )
+                period_sort = register_object.period_from.period
+
+            # Build table entry
+            entry = {
+                "has_documentation": has_documentation,
+                "register_object": register_object,
+                "date": day,
+                "date_sort": day_sort,
+                "period": period,
+                "period_sort": period_sort,
+                "groups": register_object.group_names,
+                "subject": register_object.subject.name
+                if isinstance(register_object, ExtraLesson)
+                else _("Event"),
+            }
+            if has_documentation:
+                doc = filtered_documentations[0]
+                entry["topic"] = doc.topic
+                entry["homework"] = doc.homework
+                entry["group_note"] = doc.group_note
+            register_objects.append(entry)
+
+        # Sort table entries by date and period and configure table
+        register_objects = sorted(register_objects, key=itemgetter("date_sort", "period_sort"))
+        return register_objects
+    return []
diff --git a/aleksis/apps/alsijil/views.py b/aleksis/apps/alsijil/views.py
index b2378f891f579b328c9c51114a7d212adba5d1ec..6a597bbfdaf7756ff7df28342048762f061b7505 100644
--- a/aleksis/apps/alsijil/views.py
+++ b/aleksis/apps/alsijil/views.py
@@ -18,7 +18,7 @@ from django.views.generic import DetailView
 
 import reversion
 from calendarweek import CalendarWeek
-from django_tables2 import SingleTableView
+from django_tables2 import RequestConfig, SingleTableView
 from reversion.views import RevisionMixin
 from rules.contrib.views import PermissionRequiredMixin, permission_required
 
@@ -40,6 +40,7 @@ from .forms import (
     AssignGroupRoleForm,
     ExcuseTypeForm,
     ExtraMarkForm,
+    FilterRegisterObjectForm,
     GroupRoleAssignmentEditForm,
     GroupRoleForm,
     LessonDocumentationForm,
@@ -55,9 +56,10 @@ from .models import (
     LessonDocumentation,
     PersonalNote,
 )
-from .tables import ExcuseTypeTable, ExtraMarkTable, GroupRoleTable
+from .tables import ExcuseTypeTable, ExtraMarkTable, GroupRoleTable, RegisterObjectTable
 from .util.alsijil_helpers import (
     annotate_documentations,
+    generate_list_of_all_register_objects,
     get_register_object_by_pk,
     get_timetable_instance_by_pk,
     register_objects_sorter,
@@ -894,6 +896,16 @@ def overview_person(request: HttpRequest, id_: Optional[int] = None) -> HttpResp
     context["excuse_types"] = excuse_types
     context["extra_marks"] = extra_marks
 
+    # Build filter with own form and logic as django-filter can't work with different models
+    filter_form = FilterRegisterObjectForm(request, True, request.GET or None)
+    filter_dict = filter_form.cleaned_data if filter_form.is_valid() else {}
+    filter_dict["person"] = person
+    context["filter_form"] = filter_form
+    register_objects = generate_list_of_all_register_objects(filter_dict)
+    if register_objects:
+        table = RegisterObjectTable(register_objects)
+        RequestConfig(request,).configure(table)  # paginate={"per_page": 100}
+        context["register_object_table"] = table
     return render(request, "alsijil/class_register/person.html", context)