diff --git a/smash/web/api_views/serialization_utils.py b/smash/web/api_views/serialization_utils.py
index 2f75ab119f04eac917af3473de5b9d79336397d4..165eeac4bef8fb0987b1369148aed4766bd042c8 100644
--- a/smash/web/api_views/serialization_utils.py
+++ b/smash/web/api_views/serialization_utils.py
@@ -21,4 +21,25 @@ def location_to_str(location):
     result = ""
     if location is not None:
         result = unicode(location.name)
-    return result
\ No newline at end of file
+    return result
+
+
+def add_column(result, name, field_name, column_list, param, columns_used_in_study=None, visible_param=None,
+               sortable=True):
+    add = True
+    if columns_used_in_study:
+        add = getattr(columns_used_in_study, field_name)
+    if add:
+        if visible_param is not None:
+            visible = visible_param
+        elif column_list is None:
+            visible = True
+        else:
+            visible = getattr(column_list, field_name)
+        result.append({
+            "type": field_name,
+            "name": name,
+            "filter": param,
+            "visible": visible,
+            "sortable": sortable
+        })
diff --git a/smash/web/api_views/subject.py b/smash/web/api_views/subject.py
index 5a43f3924846ad29f1ec7337487630747822a37f..2d7cb49ae64d187ad658ff0b443057637c46ff6a 100644
--- a/smash/web/api_views/subject.py
+++ b/smash/web/api_views/subject.py
@@ -5,7 +5,7 @@ from django.db.models import Count, Case, When, Min, Max
 from django.db.models import Q
 from django.http import JsonResponse
 
-from web.api_views.serialization_utils import bool_to_yes_no, flying_team_to_str,location_to_str
+from web.api_views.serialization_utils import bool_to_yes_no, flying_team_to_str, location_to_str, add_column
 from web.models import StudySubject, Visit, Appointment, Subject, SubjectColumns, StudyColumns, Study, ContactAttempt
 from web.models.constants import SUBJECT_TYPE_CHOICES, GLOBAL_STUDY_ID
 from web.models.study_subject_list import SUBJECT_LIST_GENERIC, SUBJECT_LIST_NO_VISIT, SUBJECT_LIST_REQUIRE_CONTACT, \
@@ -32,25 +32,6 @@ def referrals(request):
     })
 
 
-def add_column(result, name, field_name, column_list, param, columns_used_in_study=None, visible_param=None):
-    add = True
-    if columns_used_in_study:
-        add = getattr(columns_used_in_study, field_name)
-    if add:
-        if visible_param is not None:
-            visible = visible_param
-        elif column_list is None:
-            visible = True
-        else:
-            visible = getattr(column_list, field_name)
-        result.append({
-            "type": field_name,
-            "name": name,
-            "filter": param,
-            "visible": visible
-        })
-
-
 @login_required
 def get_subject_columns(request, subject_list_type):
     study = Study.objects.filter(id=GLOBAL_STUDY_ID)[0]
@@ -81,7 +62,7 @@ def get_subject_columns(request, subject_list_type):
     add_column(result, "Postponed", "postponed", study_subject_columns, "yes_no_filter", study.columns)
     add_column(result, "Info sent", "information_sent", study_subject_columns, "yes_no_filter", study.columns)
     add_column(result, "Type", "type", study_subject_columns, "type_filter", study.columns)
-    add_column(result, "Edit", "edit", None, None)
+    add_column(result, "Edit", "edit", None, None, sortable=False)
     for visit_number in range(1, 9):
         visit_key = "visit_" + str(visit_number)
         add_column(result, "Visit " + str(visit_number), visit_key, None, "visit_filter",
diff --git a/smash/web/api_views/visit.py b/smash/web/api_views/visit.py
index 06ecbaae8c92b496e6fd9d8df5bc16aec3df0277..80eef6027e5bc1adca60ee4c84c92bceb25d0751 100644
--- a/smash/web/api_views/visit.py
+++ b/smash/web/api_views/visit.py
@@ -4,7 +4,7 @@ from django.contrib.auth.decorators import login_required
 from django.db.models import Q
 from django.http import JsonResponse
 
-from web.api_views.serialization_utils import bool_to_yes_no, flying_team_to_str, location_to_str
+from web.api_views.serialization_utils import bool_to_yes_no, flying_team_to_str, location_to_str, add_column
 from web.models import AppointmentType, Appointment
 from web.models import SubjectColumns
 from web.models import Visit, Study, VisitColumns, StudyVisitList, StudyColumns
@@ -18,25 +18,6 @@ from web.views.notifications import get_unfinished_visits, get_active_visits_wit
 logger = logging.getLogger(__name__)
 
 
-def add_column(result, name, field_name, column_list, param, columns_used_in_study=None, visible_param=None):
-    add = True
-    if columns_used_in_study:
-        add = getattr(columns_used_in_study, field_name)
-    if add:
-        if visible_param is not None:
-            visible = visible_param
-        elif column_list is None:
-            visible = True
-        else:
-            visible = getattr(column_list, field_name)
-        result.append({
-            "type": field_name,
-            "name": name,
-            "filter": param,
-            "visible": visible
-        })
-
-
 # noinspection PyUnusedLocal
 @login_required
 def get_visit_columns(request, visit_list_type):
@@ -66,12 +47,14 @@ def get_visit_columns(request, visit_list_type):
     add_column(result, "Post mail sent", "post_mail_sent", visit_columns, "yes_no_filter")
     add_column(result, "Visit number", "visit_number", visit_columns, "integer_filter")
     add_column(result, "Appointments in progress", "visible_appointment_types_in_progress", visit_list,
-               "appointment_type_filter")
-    add_column(result, "Done appointments", "visible_appointment_types_done", visit_list, "appointment_type_filter")
+               "appointment_type_filter", sortable=False)
+    add_column(result, "Done appointments", "visible_appointment_types_done", visit_list, "appointment_type_filter",
+               sortable=False)
     add_column(result, "Missing appointments", "visible_appointment_types_missing", visit_list,
-               "appointment_type_filter")
-    add_column(result, "All appointments", "visible_appointment_types", visit_columns, "appointment_type_filter")
-    add_column(result, "Edit", "edit", None, None)
+               "appointment_type_filter", sortable=False)
+    add_column(result, "All appointments", "visible_appointment_types", visit_columns, "appointment_type_filter",
+               sortable=False)
+    add_column(result, "Edit", "edit", None, None, sortable=False)
 
     return JsonResponse({"columns": result})
 
@@ -124,6 +107,31 @@ def get_visits_order(visits_to_be_ordered, order_column, order_direction):
     return result
 
 
+def filter_by_appointment_in_progress(visits_to_filter, appointment_type):
+    result = visits_to_filter.filter(Q(appointment__appointment_types=appointment_type) & Q(
+        appointment__status=Appointment.APPOINTMENT_STATUS_SCHEDULED))
+    return result
+
+
+def filter_by_appointment_done(visits_to_filter, appointment_type):
+    result = visits_to_filter.filter(Q(appointment__appointment_types=appointment_type) & Q(
+        appointment__status=Appointment.APPOINTMENT_STATUS_FINISHED))
+    return result
+
+
+def filter_by_appointment_missing(visits_to_filter, appointment_type):
+    result = filter_by_appointment(visits_to_filter, appointment_type).exclude(
+        Q(appointment__appointment_types=appointment_type), Q(
+            appointment__status=Appointment.APPOINTMENT_STATUS_FINISHED) | Q(
+            appointment__status=Appointment.APPOINTMENT_STATUS_SCHEDULED))
+    return result
+
+
+def filter_by_appointment(visits_to_filter, appointment_type):
+    result = visits_to_filter.filter(appointment_types=appointment_type)
+    return result
+
+
 def get_visits_filtered(visits_to_be_filtered, filters):
     result = visits_to_be_filtered
     for row in filters:
@@ -143,6 +151,14 @@ def get_visits_filtered(visits_to_be_filtered, filters):
             result = result.filter(post_mail_sent=(value == "true"))
         elif column == "visit_number":
             result = result.filter(visit_number=int(value))
+        elif column == "visible_appointment_types_in_progress":
+            result = filter_by_appointment_in_progress(result, value)
+        elif column == "visible_appointment_types_done":
+            result = filter_by_appointment_done(result, value)
+        elif column == "visible_appointment_types_missing":
+            result = filter_by_appointment_missing(result, value)
+        elif column == "visible_appointment_types":
+            result = filter_by_appointment(result, value)
         else:
             message = "UNKNOWN filter: "
             if column is None:
diff --git a/smash/web/static/js/smash.js b/smash/web/static/js/smash.js
index aa9575cd678eb5099434ab93c12aa9264463c9ec..6da68884c152b60a3e56823dc4e808d4b604e76c 100644
--- a/smash/web/static/js/smash.js
+++ b/smash/web/static/js/smash.js
@@ -83,7 +83,7 @@ if (!String.prototype.startsWith) {
     };
 }
 
-function createColumn(dataType, name, filter, visible, renderFunction) {
+function createColumn(dataType, name, filter, visible, sortable, renderFunction) {
     if (renderFunction === undefined) {
         renderFunction = function (data, type, row, meta) {
             return row[dataType];
@@ -94,7 +94,8 @@ function createColumn(dataType, name, filter, visible, renderFunction) {
         "name": name,
         "filter": filter,
         "visible": visible,
-        "render": renderFunction
+        "render": renderFunction,
+        "sortable": sortable
     };
 }
 
@@ -103,7 +104,7 @@ function getColumns(columns, getSubjectEditUrl) {
     for (var i = 0; i < columns.length; i++) {
         var columnRow = columns[i];
         if (columnRow.type === "edit") {
-            result.push(createColumn("id", columnRow.name, columnRow.filter, columnRow.visible, function (data, type, row) {
+            result.push(createColumn("id", columnRow.name, columnRow.filter, columnRow.visible, columnRow.sortable, function (data, type, row) {
                 var url = getSubjectEditUrl(row.id.toString());
 
                 return '<a href="' + url + '" type="button" class="btn btn-block btn-default">Edit</a>';
@@ -116,10 +117,10 @@ function getColumns(columns, getSubjectEditUrl) {
                 };
             })();
 
-            result.push(createColumn(columnRow.type, columnRow.name, columnRow.filter, columnRow.visible, renderFunction));
+            result.push(createColumn(columnRow.type, columnRow.name, columnRow.filter, columnRow.visible, columnRow.sortable, renderFunction));
 
         } else {
-            result.push(createColumn(columnRow.type, columnRow.name, columnRow.filter, columnRow.visible));
+            result.push(createColumn(columnRow.type, columnRow.name, columnRow.filter, columnRow.visible, columnRow.sortable));
         }
     }
     return result;
@@ -212,6 +213,7 @@ function createTable(params) {
     var subject_types_url = params.subject_types_url;
     var locations_url = params.locations_url;
     var flying_teams_url = params.flying_teams_url;
+    var appointment_types_url = params.appointment_types_url;
     var subjects_url = params.subjects_url;
     var columnsDefinition = params.columns;
 
@@ -277,6 +279,19 @@ function createTable(params) {
         });
     });
 
+    $(tableElement).find('tfoot div[name="appointment_type_filter"]').each(function () {
+        var obj = $(this);
+        obj.html('<select style="width:80px"><option value selected="selected">---</option></select>');
+        var select = $('select', obj);
+        $.get(appointment_types_url, function (data) {
+            $.each(data.appointment_types, function (index, appointment_type) {
+                select.append('<option value="' + appointment_type.id + '">' + appointment_type.type + '</option>');
+            });
+            if (worker_locations.length === 1) {
+                select.val(worker_locations[0].id);
+            }
+        });
+    });
 
     $(tableElement).find('tfoot div[name="type_filter"]').each(function () {
         var obj = $(this);
@@ -296,7 +311,12 @@ function createTable(params) {
         for (var i = 0; i < columnsDefinition.length; i++) {
             var column = columnsDefinition[i];
             columns.push({"data": column.type});
-            columnDefs.push({"targets": i, "render": column.render, visible: column.visible});
+            columnDefs.push({
+                "targets": i,
+                "render": column.render,
+                visible: column.visible,
+                orderable: column.sortable
+            });
         }
 
 
@@ -333,7 +353,6 @@ function createTable(params) {
 
         // Get the column API object
         var column = table.column($(this).attr('data-column'));
-        console.log($(this).attr('data-column'));
         // Toggle the visibility
         column.visible(visible);
     });
diff --git a/smash/web/templates/visits/index.html b/smash/web/templates/visits/index.html
index d3dc4a027cb7d5b1e05b79eb2b98c7870f80b54c..eb25d83536ca7336b78e770abc4c2e9aae082f63 100644
--- a/smash/web/templates/visits/index.html
+++ b/smash/web/templates/visits/index.html
@@ -44,6 +44,7 @@
             createVisitsTable({
                 locations_url: "{% url 'web.api.locations' %}",
                 flying_teams_url: "{% url 'web.api.flying_teams' %}",
+                appointment_types_url: "{% url 'web.api.appointment_types' %}",
                 subjects_url: "{% url 'web.api.visits' visit_list_type %}",
                 tableElement: document.getElementById("table"),
                 columns: getColumns(data.columns, getVisitEditUrl),