diff --git a/smash/web/api_views/visit.py b/smash/web/api_views/visit.py
index d8ce0613f87b839a92b7104984bff06a54211430..e426440ad4abdb5615fb9a99f30f2f3902aed725 100644
--- a/smash/web/api_views/visit.py
+++ b/smash/web/api_views/visit.py
@@ -6,7 +6,11 @@ from django.http import JsonResponse
 from web.models import SubjectColumns
 from web.models import Visit, Study, VisitColumns, StudyVisitList
 from web.models.constants import GLOBAL_STUDY_ID
-from web.models.study_visit_list import VISIT_LIST_GENERIC
+from web.models.study_visit_list import VISIT_LIST_GENERIC, VISIT_LIST_EXCEEDED_TIME, VISIT_LIST_UNFINISHED, \
+    VISIT_LIST_MISSING_APPOINTMENTS, VISIT_LIST_APPROACHING_WITHOUT_APPOINTMENTS, \
+    VISIT_LIST_APPROACHING_FOR_MAIL_CONTACT
+from web.views.notifications import get_unfinished_visits, get_active_visits_with_missing_appointments, \
+    get_approaching_visits_without_appointments, get_approaching_visits_for_mail_contact, get_exceeded_visits
 
 logger = logging.getLogger(__name__)
 
@@ -62,6 +66,16 @@ def get_visit_columns(request, visit_list_type):
 def get_visits(request, visit_type):
     if visit_type == VISIT_LIST_GENERIC:
         return Visit.objects.all()
+    elif visit_type == VISIT_LIST_EXCEEDED_TIME:
+        return get_exceeded_visits(request.user).all()
+    elif visit_type == VISIT_LIST_UNFINISHED:
+        return get_unfinished_visits(request.user).all()
+    elif visit_type == VISIT_LIST_MISSING_APPOINTMENTS:
+        return get_active_visits_with_missing_appointments(request.user).all()
+    elif visit_type == VISIT_LIST_APPROACHING_WITHOUT_APPOINTMENTS:
+        return get_approaching_visits_without_appointments(request.user).all()
+    elif visit_type == VISIT_LIST_APPROACHING_FOR_MAIL_CONTACT:
+        return get_approaching_visits_for_mail_contact(request.user).all()
     else:
         raise TypeError("Unknown query type: " + visit_type)
 
@@ -76,6 +90,16 @@ def get_visits_order(visits_to_be_ordered, order_column, order_direction):
         result = visits_to_be_ordered.order_by(order_direction + 'subject__subject__first_name')
     elif order_column == "last_name":
         result = visits_to_be_ordered.order_by(order_direction + 'subject__subject__last_name')
+    elif order_column == "datetime_begin":
+        result = visits_to_be_ordered.order_by(order_direction + 'datetime_begin')
+    elif order_column == "datetime_end":
+        result = visits_to_be_ordered.order_by(order_direction + 'datetime_end')
+    elif order_column == "is_finished":
+        result = visits_to_be_ordered.order_by(order_direction + 'is_finished')
+    elif order_column == "post_mail_sent":
+        result = visits_to_be_ordered.order_by(order_direction + 'post_mail_sent')
+    elif order_column == "visit_number":
+        result = visits_to_be_ordered.order_by(order_direction + 'visit_number')
     else:
         logger.warn("Unknown sort column: " + str(order_column))
     return result
@@ -90,6 +114,12 @@ def get_visits_filtered(visits_to_be_filtered, filters):
             result = result.filter(subject__subject__first_name__icontains=value)
         elif column == "last_name":
             result = result.filter(subject__subject__last_name__icontains=value)
+        elif column == "is_finished":
+            result = result.filter(is_finished=(value == "true"))
+        elif column == "post_mail_sent":
+            result = result.filter(post_mail_sent=(value == "true"))
+        elif column == "visit_number":
+            result = result.filter(visit_number=int(value))
         else:
             message = "UNKNOWN filter: "
             if column is None:
diff --git a/smash/web/models/study_visit_list.py b/smash/web/models/study_visit_list.py
index b9c926a50e1b1ccc5546b5f356b30970bdd8a0dd..10c1bf85e599e6839ac29c7761271e3adf13ade7 100644
--- a/smash/web/models/study_visit_list.py
+++ b/smash/web/models/study_visit_list.py
@@ -4,9 +4,19 @@ from django.db import models
 from web.models import Study, SubjectColumns, VisitColumns
 
 VISIT_LIST_GENERIC = "GENERIC"
+VISIT_LIST_EXCEEDED_TIME = "EXCEEDED_TIME"
+VISIT_LIST_UNFINISHED = "UNFINISHED"
+VISIT_LIST_MISSING_APPOINTMENTS = "MISSING_APPOINTMENTS"
+VISIT_LIST_APPROACHING_WITHOUT_APPOINTMENTS = "APPROACHING_WITHOUT_APPOINTMENTS"
+VISIT_LIST_APPROACHING_FOR_MAIL_CONTACT = "APPROACHING_FOR_MAIL_CONTACT"
 
 VISIT_LIST_CHOICES = {
     VISIT_LIST_GENERIC: 'Generic',
+    VISIT_LIST_EXCEEDED_TIME: 'exceeded visit time',
+    VISIT_LIST_UNFINISHED: 'unfinished visits',
+    VISIT_LIST_MISSING_APPOINTMENTS: 'visits with missing appointments',
+    VISIT_LIST_APPROACHING_WITHOUT_APPOINTMENTS: 'approaching visits',
+    VISIT_LIST_APPROACHING_FOR_MAIL_CONTACT: 'post mail for approaching visits',
 }
 
 
diff --git a/smash/web/static/js/smash.js b/smash/web/static/js/smash.js
index 3bfb678925183ceaf0ac7f5f88e61d4700ca48ce..5162648810f60f1d2dae102b53dffba405f7f929 100644
--- a/smash/web/static/js/smash.js
+++ b/smash/web/static/js/smash.js
@@ -70,4 +70,266 @@ function showErrorInfo(content) {
     $(errorDialogDiv).attr("role", "dialog");
     $(errorDialogDiv).addClass("modal modal-danger fade");
     $(errorDialogDiv).modal("show");
-}
\ No newline at end of file
+}
+
+//------------------------------------------------------
+// common code for dynamic Data Tables
+//------------------------------------------------------
+
+if (!String.prototype.startsWith) {
+    String.prototype.startsWith = function (searchString, position) {
+        position = position || 0;
+        return this.indexOf(searchString, position) === position;
+    };
+}
+
+function createColumn(dataType, name, filter, visible, renderFunction) {
+    if (renderFunction === undefined) {
+        renderFunction = function (data, type, row, meta) {
+            return row[dataType];
+        }
+    }
+    return {
+        "type": dataType,
+        "name": name,
+        "filter": filter,
+        "visible": visible,
+        "render": renderFunction
+    };
+}
+
+function getColumns(columns, getSubjectEditUrl) {
+    var result = [];
+    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) {
+                var url = getSubjectEditUrl(row.id.toString());
+
+                return '<a href="' + url + '" type="button" class="btn btn-block btn-default">Edit</a>';
+            }));
+        } else if (/^visit_[0-9]+$/.test(columnRow.type)) {
+            var renderFunction = (function () {
+                var x = columnRow.type.replace("visit_", "");
+                return function (data, type, row) {
+                    console.log("render", row.visits);
+                    return create_visit_row(row.visits[x - 1]);
+                };
+            })();
+
+            result.push(createColumn(columnRow.type, columnRow.name, columnRow.filter, columnRow.visible, renderFunction));
+
+        } else {
+            result.push(createColumn(columnRow.type, columnRow.name, columnRow.filter, columnRow.visible));
+        }
+    }
+    return result;
+}
+
+function createHeader(columnsDefinition) {
+    var header = document.createElement("thead");
+    var headerRow = document.createElement("tr");
+    header.appendChild(headerRow);
+    for (var i = 0; i < columnsDefinition.length; i++) {
+        var column = columnsDefinition[i];
+        var element = document.createElement("th");
+        element.innerHTML = column.name;
+        headerRow.appendChild(element);
+    }
+    return header;
+}
+
+function createFilter(columnsDefinition) {
+    var footer = document.createElement("tfoot");
+    footer.style.display = "table-header-group";
+    var footerRow = document.createElement("tr");
+    footer.appendChild(footerRow);
+    for (var i = 0; i < columnsDefinition.length; i++) {
+        var column = columnsDefinition[i];
+        var element = document.createElement("th");
+        if (column.filter !== null) {
+            element.innerHTML = "<div name='" + column.filter + "'>" + column.name + "</div>";
+        }
+        footerRow.appendChild(element);
+    }
+    return footer;
+}
+
+function create_visit_row(visit) {
+    var color = "white";
+    var text = "---";
+    if (visit !== undefined && visit !== null) {
+        if (visit.status === "DONE") {
+            color = "green";
+            text = "OK";
+        } else if (visit.status === "MISSED") {
+            color = "pink";
+            text = "MISSED";
+        } else if (visit.status === "UPCOMING") {
+            color = "#00ffff";
+            text = "UPCOMING";
+        } else if (visit.status === "EXCEEDED") {
+            color = "orange";
+            text = "EXCEEDED";
+        } else if (visit.status === "SHOULD_BE_IN_PROGRESS") {
+            color = "orange";
+            text = "IN PROGRESS (NO APPOINTMENTS)";
+        } else if (visit.status === "IN_PROGRESS") {
+            color = "lightgreen";
+            text = "IN PROGRESS";
+        }
+        text += "<br/>" + visit.datetime_start + " - " + visit.datetime_end;
+    }
+    return "<div style='background-color:" + color + "';width:100%;height:100%>" + text + "</div>";
+}
+
+function createVisibilityCheckboxes(checkboxesElement, columns) {
+    var row = null;
+    for (var i = 0; i < columns.length; i++) {
+        if (i % 10 === 0) {
+            row = document.createElement("div");
+            row.style.display = "table-row";
+            checkboxesElement.appendChild(row);
+        }
+        var column = columns[i];
+        var element = document.createElement("div");
+        element.style.display = "table-cell";
+        var checked = "";
+        if (column.visible) {
+            checked = "checked";
+        }
+        element.innerHTML = "<input type='checkbox' " + checked + " data-column='" + i + "' name='" + column.type + "'/>" + column.name;
+        row.appendChild(element);
+    }
+
+}
+
+function createTable(params) {
+    var tableElement = params.tableElement;
+    var worker_locations = params.worker_locations;
+    var subject_types_url = params.subject_types_url;
+    var locations_url = params.locations_url;
+    var flying_teams_url = params.flying_teams_url;
+    var subjects_url = params.subjects_url;
+    var columnsDefinition = params.columns;
+
+    tableElement.appendChild(createHeader(columnsDefinition));
+    tableElement.appendChild(createFilter(columnsDefinition));
+    tableElement.appendChild(document.createElement("tbody"));
+    createVisibilityCheckboxes(params.checkboxesElement, columnsDefinition);
+
+    var table;
+    $(tableElement).find('tfoot div[name="string_filter"]').each(function () {
+        var title = $(this).text();
+        $(this).html('<input type="text" style="width:80px" placeholder="' + title + '" />');
+    });
+
+    $(tableElement).find('tfoot div[name="yes_no_filter"]').each(function () {
+        $(this).html('<select style="width:60px" ><option value selected="selected">---</option><option value="true">YES</option><option value="false">NO</option></select>');
+    });
+
+    $(tableElement).find('tfoot div[name="visit_filter"]').each(function () {
+        $(this).html('<select style="width:60px" >' +
+            '<option value selected="selected">---</option>' +
+            '<option value="MISSED">MISSED</option>' +
+            '<option value="DONE">DONE</option>' +
+            '<option value="EXCEED">EXCEED</option>' +
+            '<option value="IN_PROGRESS">IN PROGRESS</option>' +
+            '<option value="SHOULD_BE_IN_PROGRESS">SHOULD BE IN PROGRESS</option>' +
+            '<option value="UPCOMING">UPCOMING</option>' +
+            '</select>');
+    });
+
+    $(tableElement).find('tfoot div[name="location_filter"]').each(function () {
+        var obj = $(this);
+        obj.html('<select style="width:80px"><option value selected="selected">---</option></select>');
+        var select = $('select', obj);
+        $.get(locations_url, function (data) {
+            $.each(data.locations, function (index, location) {
+                select.append('<option value="' + location.id + '">' + location.name + '</option>');
+            });
+            if (worker_locations.length === 1) {
+                select.val(worker_locations[0].id);
+            }
+        });
+    });
+
+    $(tableElement).find('tfoot div[name="flying_team_filter"]').each(function () {
+        var obj = $(this);
+        obj.html('<select style="width:80px"><option value selected="selected">---</option></select>');
+        var select = $('select', obj);
+        $.get(flying_teams_url, function (data) {
+            $.each(data.flying_teams, function (index, flying_team) {
+                select.append('<option value="' + flying_team.id + '">' + flying_team.name + '</option>');
+            });
+            if (worker_locations.length === 1) {
+                select.val(worker_locations[0].id);
+            }
+        });
+    });
+
+
+    $(tableElement).find('tfoot div[name="type_filter"]').each(function () {
+        var obj = $(this);
+        obj.html('<select style="width:80px"><option value selected="selected">---</option></select>');
+        var select = $('select', obj);
+        $.get(subject_types_url, function (data) {
+            $.each(data.types, function (index, type) {
+                select.append('<option value="' + type.id + '">' + type.name + '</option>');
+            });
+        });
+    });
+
+
+    $(function () {
+        var columns = [];
+        var columnDefs = [];
+        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});
+        }
+
+
+        table = $('#table').DataTable({
+            pageLength: 25,
+            serverSide: true,
+            processing: true,
+            responsive: true,
+            ajax: subjects_url,
+            columns: columns,
+            columnDefs: columnDefs,
+            order: [[0, 'desc']]
+        });
+
+        // Apply the search
+        table.columns().every(function () {
+            var that = this;
+
+            $('input', this.footer()).on('keyup change', function () {
+                if (that.search() !== this.value) {
+                    that.search(this.value).draw();
+                }
+            });
+            $('select', this.footer()).on('keyup change', function () {
+                if (that.search() !== this.value) {
+                    that.search(this.value).draw();
+                }
+            });
+        });
+        $('#table_filter').css("display", "null");
+    });
+    $('#visible-column-checkboxes input').on('click', function (e) {
+        var visible = $(this).is(":checked");
+
+        // 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);
+    });
+}
+
+//------------------------------------------------------
+// END common code for dynamic Data Tables
+//------------------------------------------------------
diff --git a/smash/web/static/js/subject.js b/smash/web/static/js/subject.js
index 585d1b9f5b568e31ffa03dcc29fbe23fafcb45d1..434b796a2e48a78f93ecd710d02de1ad9f453efa 100644
--- a/smash/web/static/js/subject.js
+++ b/smash/web/static/js/subject.js
@@ -1,253 +1,3 @@
-if (!String.prototype.startsWith) {
-    String.prototype.startsWith = function (searchString, position) {
-        position = position || 0;
-        return this.indexOf(searchString, position) === position;
-    };
-}
-
-function createColumn(dataType, name, filter, visible, renderFunction) {
-    if (renderFunction === undefined) {
-        renderFunction = function (data, type, row, meta) {
-            return row[dataType];
-        }
-    }
-    return {
-        "type": dataType,
-        "name": name,
-        "filter": filter,
-        "visible": visible,
-        "render": renderFunction
-    };
-}
-
-function getColumns(columns, getSubjectEditUrl) {
-    var result = [];
-    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) {
-                var url = getSubjectEditUrl(row.id.toString());
-
-                return '<a href="' + url + '" type="button" class="btn btn-block btn-default">Edit</a>';
-            }));
-        } else if (columnRow.type.startsWith("visit")) {
-            var renderFunction = (function () {
-                var x = i;
-                return function (data, type, row) {
-                    return create_visit_row(row.visits[x - 1]);
-                };
-            })();
-
-            result.push(createColumn(columnRow.type, columnRow.name, columnRow.filter, columnRow.visible, renderFunction));
-
-        } else {
-            result.push(createColumn(columnRow.type, columnRow.name, columnRow.filter, columnRow.visible));
-        }
-    }
-    return result;
-}
-
-function createHeader(columnsDefinition) {
-    var header = document.createElement("thead");
-    var headerRow = document.createElement("tr");
-    header.appendChild(headerRow);
-    for (var i = 0; i < columnsDefinition.length; i++) {
-        var column = columnsDefinition[i];
-        var element = document.createElement("th");
-        element.innerHTML = column.name;
-        headerRow.appendChild(element);
-    }
-    return header;
-}
-
-function createFilter(columnsDefinition) {
-    var footer = document.createElement("tfoot");
-    footer.style.display = "table-header-group";
-    var footerRow = document.createElement("tr");
-    footer.appendChild(footerRow);
-    for (var i = 0; i < columnsDefinition.length; i++) {
-        var column = columnsDefinition[i];
-        var element = document.createElement("th");
-        if (column.filter !== null) {
-            element.innerHTML = "<div name='" + column.filter + "'>" + column.name + "</div>";
-        }
-        footerRow.appendChild(element);
-    }
-    return footer;
-}
-
-function create_visit_row(visit) {
-    var color = "white";
-    var text = "---";
-    if (visit !== undefined && visit !== null) {
-        if (visit.status === "DONE") {
-            color = "green";
-            text = "OK";
-        } else if (visit.status === "MISSED") {
-            color = "pink";
-            text = "MISSED";
-        } else if (visit.status === "UPCOMING") {
-            color = "#00ffff";
-            text = "UPCOMING";
-        } else if (visit.status === "EXCEEDED") {
-            color = "orange";
-            text = "EXCEEDED";
-        } else if (visit.status === "SHOULD_BE_IN_PROGRESS") {
-            color = "orange";
-            text = "IN PROGRESS (NO APPOINTMENTS)";
-        } else if (visit.status === "IN_PROGRESS") {
-            color = "lightgreen";
-            text = "IN PROGRESS";
-        }
-        text += "<br/>" + visit.datetime_start + " - " + visit.datetime_end;
-    }
-    return "<div style='background-color:" + color + "';width:100%;height:100%>" + text + "</div>";
-}
-
-function createVisibilityCheckboxes(checkboxesElement, columns) {
-    var row = null;
-    for (var i = 0; i < columns.length; i++) {
-        if (i % 10 === 0) {
-            row = document.createElement("div");
-            row.style.display = "table-row";
-            checkboxesElement.appendChild(row);
-        }
-        var column = columns[i];
-        var element = document.createElement("div");
-        element.style.display = "table-cell";
-        var checked = "";
-        if (column.visible) {
-            checked = "checked";
-        }
-        element.innerHTML = "<input type='checkbox' " + checked + " data-column='" + i + "' name='" + column.type + "'/>" + column.name;
-        row.appendChild(element);
-    }
-
-}
-
 function createSubjectsTable(params) {
-    var tableElement = params.tableElement;
-    var worker_locations = params.worker_locations;
-    var subject_types_url = params.subject_types_url;
-    var locations_url = params.locations_url;
-    var flying_teams_url = params.flying_teams_url;
-    var subjects_url = params.subjects_url;
-    var columnsDefinition = params.columns;
-
-    tableElement.appendChild(createHeader(columnsDefinition));
-    tableElement.appendChild(createFilter(columnsDefinition));
-    tableElement.appendChild(document.createElement("tbody"));
-    createVisibilityCheckboxes(params.checkboxesElement, columnsDefinition);
-
-    var table;
-    $(tableElement).find('tfoot div[name="string_filter"]').each(function () {
-        var title = $(this).text();
-        $(this).html('<input type="text" style="width:80px" placeholder="' + title + '" />');
-    });
-
-    $(tableElement).find('tfoot div[name="yes_no_filter"]').each(function () {
-        $(this).html('<select style="width:60px" ><option value selected="selected">---</option><option value="true">YES</option><option value="false">NO</option></select>');
-    });
-
-    $(tableElement).find('tfoot div[name="visit_filter"]').each(function () {
-        $(this).html('<select style="width:60px" >' +
-            '<option value selected="selected">---</option>' +
-            '<option value="MISSED">MISSED</option>' +
-            '<option value="DONE">DONE</option>' +
-            '<option value="EXCEED">EXCEED</option>' +
-            '<option value="IN_PROGRESS">IN PROGRESS</option>' +
-            '<option value="SHOULD_BE_IN_PROGRESS">SHOULD BE IN PROGRESS</option>' +
-            '<option value="UPCOMING">UPCOMING</option>' +
-            '</select>');
-    });
-
-    $(tableElement).find('tfoot div[name="location_filter"]').each(function () {
-        var obj = $(this);
-        obj.html('<select style="width:80px"><option value selected="selected">---</option></select>');
-        var select = $('select', obj);
-        $.get(locations_url, function (data) {
-            $.each(data.locations, function (index, location) {
-                select.append('<option value="' + location.id + '">' + location.name + '</option>');
-            });
-            if (worker_locations.length === 1) {
-                select.val(worker_locations[0].id);
-            }
-        });
-    });
-
-    $(tableElement).find('tfoot div[name="flying_team_filter"]').each(function () {
-        var obj = $(this);
-        obj.html('<select style="width:80px"><option value selected="selected">---</option></select>');
-        var select = $('select', obj);
-        $.get(flying_teams_url, function (data) {
-            $.each(data.flying_teams, function (index, flying_team) {
-                select.append('<option value="' + flying_team.id + '">' + flying_team.name + '</option>');
-            });
-            if (worker_locations.length === 1) {
-                select.val(worker_locations[0].id);
-            }
-        });
-    });
-
-
-
-    $(tableElement).find('tfoot div[name="type_filter"]').each(function () {
-        var obj = $(this);
-        obj.html('<select style="width:80px"><option value selected="selected">---</option></select>');
-        var select = $('select', obj);
-        $.get(subject_types_url, function (data) {
-            $.each(data.types, function (index, type) {
-                select.append('<option value="' + type.id + '">' + type.name + '</option>');
-            });
-        });
-    });
-
-
-    $(function () {
-        var columns = [];
-        var columnDefs = [];
-        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});
-        }
-
-
-        table = $('#table').DataTable({
-            pageLength: 25,
-            serverSide: true,
-            processing: true,
-            responsive: true,
-            ajax: subjects_url,
-            columns: columns,
-            columnDefs: columnDefs,
-            order: [[0, 'desc']]
-        });
-
-        // Apply the search
-        table.columns().every(function () {
-            var that = this;
-
-            $('input', this.footer()).on('keyup change', function () {
-                if (that.search() !== this.value) {
-                    that.search(this.value).draw();
-                }
-            });
-            $('select', this.footer()).on('keyup change', function () {
-                if (that.search() !== this.value) {
-                    that.search(this.value).draw();
-                }
-            });
-        });
-        $('#table_filter').css("display", "null");
-    });
-    $('#visible-column-checkboxes input').on('click', function (e) {
-        var visible = $(this).is(":checked");
-
-        // 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);
-    });
-}
\ No newline at end of file
+    return createTable(params);
+}
diff --git a/smash/web/static/js/visit.js b/smash/web/static/js/visit.js
index c4eac088fd1fe16a7bad5f2baa307481184bc58b..a2636ed219748484b648296a736608d14732589c 100644
--- a/smash/web/static/js/visit.js
+++ b/smash/web/static/js/visit.js
@@ -10,3 +10,8 @@ function visit_dates_behaviour(startDateInput, endDateInput) {
         }
     });
 }
+
+
+function createVisitsTable(params) {
+    return createTable(params);
+}
diff --git a/smash/web/templates/visits/index.html b/smash/web/templates/visits/index.html
index 6d82fe9098392bd9c903d3442379326a71c25378..d3dc4a027cb7d5b1e05b79eb2b98c7870f80b54c 100644
--- a/smash/web/templates/visits/index.html
+++ b/smash/web/templates/visits/index.html
@@ -7,9 +7,6 @@
     <!-- DataTables -->
     <link rel="stylesheet" href="{% static 'AdminLTE/plugins/datatables/dataTables.bootstrap.css' %}">
 
-    <!-- fullCalendar 2.2.5-->
-    <link rel="stylesheet" href="{% static 'AdminLTE/plugins/fullcalendar/fullcalendar.min.css' %}">
-    <link rel="stylesheet" href="{% static 'AdminLTE/plugins/fullcalendar/fullcalendar.print.css' %}" media="print">
 {% endblock styles %}
 
 {% block ui_active_tab %}'visits'{% endblock ui_active_tab %}
@@ -23,62 +20,12 @@
 {% endblock breadcrumb %}
 
 {% block maincontent %}
-
-    <div>
-        <a href="{% url 'web.views.visit_add' %}" class="btn btn-app">
-            <i class="fa fa-plus"></i>
-            Add new visit
-        </a>
+    <div class="box-body">
+        <table id="table" class="table table-bordered table-striped table-responsive">
+        </table>
     </div>
-
-    <div>
-        <div>
-            {% if visit_list %}
-                <table id="visit_table" class="table table-bordered table-striped">
-                    <thead>
-                    <tr>
-                        <th>Subject name</th>
-                        <th>Full information</th>
-                        <th>Visit begins</th>
-                        <th>Visit ends</th>
-                        <th>Finished?</th>
-                    </tr>
-                    </thead>
-                    <tbody>
-                    {% for visit in visit_list %}
-                        <tr>
-                            <td>{{ visit.subject.first_name }} {{ visit.subject.last_name }}</td>
-                            <td>
-                                <a href="{% url 'web.views.visit_details' visit.id %}" type="button"
-                                   class="btn btn-block btn-default">Details</a>
-                            </td>
-                            <td>
-                                {{ visit.datetime_begin }}
-                            </td>
-                            <td>
-                                {{ visit.datetime_end }}
-                            </td>
-                            <td>
-                                {% if visit.is_finished %}
-                                    <button type="button" class="btn btn-block btn-success">YES</button>
-                                {% else %}
-                                    <button type="button" class="btn btn-block btn-danger">NO</button>
-                                {% endif %}
-                            </td>
-
-                        </tr>
-                    {% endfor %}
-                    </tbody>
-                </table>
-
-            {% else %}
-                <p>No visits to display.</p>
-            {% endif %}
-
-            <hr/>
-
-        </div>
-
+    <h3>Visible columns</h3>
+    <div id="visible-column-checkboxes" style="display:table; width:100%">
     </div>
 {% endblock maincontent %}
 
@@ -87,19 +34,21 @@
 
     <script src="{% static 'AdminLTE/plugins/datatables/jquery.dataTables.min.js' %}"></script>
     <script src="{% static 'AdminLTE/plugins/datatables/dataTables.bootstrap.min.js' %}"></script>
-    <script src="{% static 'AdminLTE/plugins/moment.js/moment.min.js' %}"></script>
-    <script src="{% static 'AdminLTE/plugins/fullcalendar/fullcalendar.min.js' %}"></script>
+    <script src="{% static 'js/visit.js' %}"></script>
 
     <script>
-        $(function () {
-            $('#planning_table, #approaching_table').DataTable({
-                "paging": true,
-                "lengthChange": false,
-                "searching": true,
-                "ordering": true,
-                "info": true,
-                "autoWidth": false
-            });
+        function getVisitEditUrl(id) {
+            return "{% url 'web.views.visit_details' 12345678 %}".replace(/12345678/, id);
+        }
+        $.get("{% url 'web.api.visits.columns' visit_list_type %}", function (data) {
+            createVisitsTable({
+                locations_url: "{% url 'web.api.locations' %}",
+                flying_teams_url: "{% url 'web.api.flying_teams' %}",
+                subjects_url: "{% url 'web.api.visits' visit_list_type %}",
+                tableElement: document.getElementById("table"),
+                columns: getColumns(data.columns, getVisitEditUrl),
+                checkboxesElement: document.getElementById("visible-column-checkboxes")
+            })
         });
     </script>
 {% endblock scripts %}
diff --git a/smash/web/tests/api_views/test_visit.py b/smash/web/tests/api_views/test_visit.py
index 09760d48df1d96716e76f2c1a81974ef4aa0728e..63049c963cf1869185ae4912000e3a48d0078418 100644
--- a/smash/web/tests/api_views/test_visit.py
+++ b/smash/web/tests/api_views/test_visit.py
@@ -4,7 +4,9 @@ import logging
 
 from django.urls import reverse
 
-from web.models.study_visit_list import VISIT_LIST_GENERIC
+from web.models.study_visit_list import VISIT_LIST_GENERIC, VISIT_LIST_EXCEEDED_TIME, VISIT_LIST_UNFINISHED, \
+    VISIT_LIST_MISSING_APPOINTMENTS, VISIT_LIST_APPROACHING_WITHOUT_APPOINTMENTS, \
+    VISIT_LIST_APPROACHING_FOR_MAIL_CONTACT
 from web.tests import LoggedInWithWorkerTestCase
 from web.tests.functions import create_study_subject, create_get_suffix, create_visit
 
@@ -23,10 +25,33 @@ class TestVisitApi(LoggedInWithWorkerTestCase):
         columns = json.loads(response.content)['columns']
         self.assertTrue(len(columns) > 0)
 
-    def test_visits_general(self):
-        response = self.client.get(reverse('web.api.visits', kwargs={'visit_list_type': VISIT_LIST_GENERIC}))
+    def test_visits_exceeded(self):
+        response = self.client.get(reverse('web.api.visits', kwargs={'visit_list_type': VISIT_LIST_EXCEEDED_TIME}))
         self.assertEqual(response.status_code, 200)
 
+    def test_visits_unfinished(self):
+        response = self.client.get(reverse('web.api.visits', kwargs={'visit_list_type': VISIT_LIST_UNFINISHED}))
+        self.assertEqual(response.status_code, 200)
+
+    def test_visits_missing_appointments(self):
+        response = self.client.get(
+            reverse('web.api.visits', kwargs={'visit_list_type': VISIT_LIST_MISSING_APPOINTMENTS}))
+        self.assertEqual(response.status_code, 200)
+
+    def test_visits_approaching_without_appointments(self):
+        response = self.client.get(
+            reverse('web.api.visits', kwargs={'visit_list_type': VISIT_LIST_APPROACHING_WITHOUT_APPOINTMENTS}))
+        self.assertEqual(response.status_code, 200)
+
+    def test_visits_approaching_for_mail_contact(self):
+        response = self.client.get(
+            reverse('web.api.visits', kwargs={'visit_list_type': VISIT_LIST_APPROACHING_FOR_MAIL_CONTACT}))
+        self.assertEqual(response.status_code, 200)
+
+    def test_visits_invalid(self):
+        with self.assertRaises(TypeError):
+            self.client.get(reverse('web.api.visits', kwargs={'visit_list_type': "invalid_type"}))
+
     def test_visits_general_search(self):
         name = "Piotrek"
         self.study_subject.subject.first_name = name
diff --git a/smash/web/views/visit.py b/smash/web/views/visit.py
index 0dd67c3b3f67cabb631f9b08ee00359081b1694a..7a1e821b71fe0b36b4b49d0ac2de19eaadefa8af 100644
--- a/smash/web/views/visit.py
+++ b/smash/web/views/visit.py
@@ -3,9 +3,10 @@ import logging
 
 from django.shortcuts import get_object_or_404, redirect
 
-from notifications import get_active_visits_with_missing_appointments, get_unfinished_visits, \
-    get_approaching_visits_without_appointments, get_approaching_visits_for_mail_contact, get_exceeded_visits, \
-    waiting_for_appointment
+from notifications import waiting_for_appointment
+from web.models.study_visit_list import VISIT_LIST_GENERIC, VISIT_LIST_MISSING_APPOINTMENTS, \
+    VISIT_LIST_APPROACHING_WITHOUT_APPOINTMENTS, VISIT_LIST_APPROACHING_FOR_MAIL_CONTACT, VISIT_LIST_EXCEEDED_TIME, \
+    VISIT_LIST_UNFINISHED
 from . import wrap_response
 from ..forms import VisitDetailForm, VisitAddForm, SubjectDetailForm, StudySubjectDetailForm
 from ..models import Visit, Appointment, StudySubject, MailTemplate
@@ -13,35 +14,32 @@ from ..models import Visit, Appointment, StudySubject, MailTemplate
 logger = logging.getLogger(__name__)
 
 
-def visits(request):
-    visit_list = Visit.objects.order_by('-datetime_begin')
-    context = {
-        'visit_list': visit_list
-    }
+def show_visits(request, visit_list_type):
+    return wrap_response(request, 'visits/index.html', {'visit_list_type': visit_list_type})
 
-    return wrap_response(request, 'visits/index.html', context)
 
+def visits(request):
+    return show_visits(request, VISIT_LIST_GENERIC)
 
-def visits_with_missing_appointments(request):
-    context = {
-        'visit_list': get_active_visits_with_missing_appointments(request.user)
-    }
 
-    return wrap_response(request, 'visits/index.html', context)
+def visits_with_missing_appointments(request):
+    return show_visits(request, VISIT_LIST_MISSING_APPOINTMENTS)
 
 
 def approaching_visits_without_appointments(request):
-    context = {
-        'visit_list': get_approaching_visits_without_appointments(request.user)
-    }
-    return wrap_response(request, 'visits/index.html', context)
+    return show_visits(request, VISIT_LIST_APPROACHING_WITHOUT_APPOINTMENTS)
 
 
 def approaching_visits_for_mail_contact(request):
-    context = {
-        'visit_list': get_approaching_visits_for_mail_contact(request.user)
-    }
-    return wrap_response(request, 'visits/index.html', context)
+    return show_visits(request, VISIT_LIST_APPROACHING_FOR_MAIL_CONTACT)
+
+
+def exceeded_visits(request):
+    return show_visits(request, VISIT_LIST_EXCEEDED_TIME)
+
+
+def unfinished_visits(request):
+    return show_visits(request, VISIT_LIST_UNFINISHED)
 
 
 def visit_details(request, id):
@@ -105,18 +103,3 @@ def visit_add(request, subject_id=-1):
         form = VisitAddForm(initial={'subject': subject})
 
     return wrap_response(request, 'visits/add.html', {'form': form})
-
-
-def exceeded_visits(request):
-    context = {
-        'visit_list': get_exceeded_visits(request.user)
-    }
-    return wrap_response(request, 'visits/index.html', context)
-
-
-def unfinished_visits(request):
-    context = {
-        'visit_list': get_unfinished_visits(request.user)
-    }
-
-    return wrap_response(request, 'visits/index.html', context)