diff --git a/aleksis/apps/alsijil/rules.py b/aleksis/apps/alsijil/rules.py
index aa80713bda0ce2e643e03e1a0f2f484b7aea297b..24852047bf6d82d94dfecd154fb140e91ac94225 100644
--- a/aleksis/apps/alsijil/rules.py
+++ b/aleksis/apps/alsijil/rules.py
@@ -318,6 +318,9 @@ view_instructions_predicate = has_person & (
 )
 add_perm("alsijil.view_instructions_rule", view_instructions_predicate)
 
+view_done_instructions_predicate = has_person & (has_global_perm("alsijil.view_instruction"))
+add_perm("alsijil.view_done_instructions_rule", view_instructions_predicate)
+
 view_instruction_predicate = has_person & (
     has_global_perm("alsijil.view_instruction")
     | is_instruction_for_person
diff --git a/aleksis/apps/alsijil/templates/alsijil/instruction/list.html b/aleksis/apps/alsijil/templates/alsijil/instruction/list.html
index eddfc6f9cc0099083dd2a7a2bbc06bbf991af0a7..cd785928bc04634cc3e36e4b28deac549e2a4450 100644
--- a/aleksis/apps/alsijil/templates/alsijil/instruction/list.html
+++ b/aleksis/apps/alsijil/templates/alsijil/instruction/list.html
@@ -7,6 +7,26 @@
 {% block browser_title %}{% blocktrans %}Instructions{% endblocktrans %}{% endblock %}
 {% block page_title %}{% blocktrans %}Instructions{% endblocktrans %}{% endblock %}
 
+{% block nav_content %}
+  <ul class="tabs tabs-transparent tabs-icons tabs-fixed-width">
+    <li class="tab">
+      <a href="#all">
+        <i class="material-icons">list</i>
+        {% trans "All instructions" %}
+      </a>
+    </li>
+    {% has_perm "alsijil.view_done_instructions_rule" user as can_view_done %}
+    {% if can_view_done %}
+      <li class="tab">
+        <a href="#done">
+          <i class="material-icons">done_all</i>
+          {% trans "Done instructions" %}
+        </a>
+      </li>
+    {% endif %}
+  </ul>
+{% endblock %}
+
 {% block content %}
   <div class="row no-margin">
     <div class="col s12">
@@ -38,8 +58,7 @@
     </div>
   </div>
 
-
-  <div class="row">
+  <div class="row" id="all">
     {% for instruction in instruction_list %}
       <div class=" col s12 m12 l6 xl4">
         <div class="card">
@@ -71,4 +90,35 @@
       </div>
     {% endfor %}
   </div>
+
+  {% has_perm "alsijil.view_done_instructions_rule" user as can_view_done %}
+  {% if can_view_done %}
+    <div class="row" id="done">
+      <table>
+        <tr>
+          <th></th>
+          {% for instruction in object_list %}
+            <th>{{ instruction.name }}</th>
+          {% endfor %}
+        </tr>
+        {% for group, instructions in done_instructions.items %}
+          <tr>
+            <th>{{ group.name }}</th>
+            {% for instruction, el in instructions.items %}
+              {% with done=el.0 docs=el.1 %}
+                <td class="{% if done %}green-text green lighten-5{% else %}red-text red lighten-5{% endif %}">
+                  <i class="material-icons left">{% if done %}check{% else %}clear{% endif %}</i>
+                  <small>
+                    {% for doc in docs %}
+                      {{ doc.date }}, {{ doc.register_object.teacher_short_names }}{% if not forloop.last %};{% endif %}
+                    {% endfor %}
+                  </small>
+                </td>
+              {% endwith %}
+            {% endfor %}
+          </tr>
+        {% endfor %}
+      </table>
+    </div>
+  {% endif %}
 {% endblock %}
diff --git a/aleksis/apps/alsijil/views.py b/aleksis/apps/alsijil/views.py
index 4605624cddf8e914e0be81a8a2ef6a6e96f74adc..7bde1d8514dbcbc8b8ff047e0651c54d73b27737 100644
--- a/aleksis/apps/alsijil/views.py
+++ b/aleksis/apps/alsijil/views.py
@@ -1,3 +1,4 @@
+from collections import OrderedDict
 from contextlib import nullcontext
 from copy import deepcopy
 from datetime import date, datetime, timedelta
@@ -1442,6 +1443,62 @@ class InstructionsListView(PermissionRequiredMixin, FilterView):
             .distinct()
         )
 
+    def get_context_data(self, **kwargs) -> dict:
+        context = super().get_context_data(**kwargs)
+
+        if not self.request.user.has_perm("alsijil.view_done_instructions_rule"):
+            return context
+
+        qs = self.object_list
+
+        docs = LessonDocumentation.objects.filter(done_instructions__in=qs)
+        done_instructions_by_group = {}
+        for doc in docs:
+            groups = doc.register_object.get_groups().all()
+            for group in groups:
+                parent_groups = group.parent_groups_recursive
+                group_members = set(group.members.all())
+                selected_groups = list(parent_groups) + [group]
+
+                match_found = False
+                for selected_group in selected_groups:
+                    if selected_group == group and match_found:
+                        # Use original group only if no matching parent group has been found
+                        continue
+
+                    # Use parent groups if the members are the same
+                    if set(selected_group.members.all()) == group_members:
+                        match_found = True
+                        done_instructions_by_group.setdefault(
+                            selected_group, dict(docs={}, instructions=set())
+                        )
+                        instructions = list(doc.done_instructions.all())
+                        for instruction in instructions:
+                            done_instructions_by_group[selected_group]["docs"].setdefault(
+                                instruction, set()
+                            )
+                            done_instructions_by_group[selected_group]["docs"][instruction].add(doc)
+                        done_instructions_by_group[selected_group]["instructions"].update(
+                            instructions
+                        )
+
+        # Sort done instructions by group name
+        done_instructions_by_group = OrderedDict(
+            sorted(done_instructions_by_group.items(), key=lambda x: x[0].short_name or x[0].name)
+        )
+
+        # Build table with instructions on x-axis and groups on y-axis
+        table_by_groups = OrderedDict()
+        for group, el in done_instructions_by_group.items():
+            done_instructions = el["instructions"]
+            table_by_groups.setdefault(group, OrderedDict())
+            for instruction in qs:
+                docs = el["docs"].get(instruction, set())
+                table_by_groups[group][instruction] = (instruction in done_instructions, docs)
+
+        context["done_instructions"] = table_by_groups
+        return context
+
 
 @method_decorator(never_cache, name="dispatch")
 class InstructionCreateView(PermissionRequiredMixin, AdvancedCreateView):