diff --git a/AKScheduling/static/AKScheduling/js/scheduling.js b/AKScheduling/static/AKScheduling/js/scheduling.js
new file mode 100644
index 0000000000000000000000000000000000000000..1c15164a2f671ef5b73fcf96d17fb36e747dc26f
--- /dev/null
+++ b/AKScheduling/static/AKScheduling/js/scheduling.js
@@ -0,0 +1,12 @@
+function loadCVs(url, callback_success, callback_error) {
+    $.ajax({
+        url: url,
+        type: 'GET',
+        success: callback_success,
+        error: callback_error
+    });
+}
+
+const default_cv_callback_error = function(response) {
+   alert("{% trans 'Cannot load current violations from server' %}");
+}
diff --git a/AKScheduling/templates/admin/AKScheduling/constraint_violations.html b/AKScheduling/templates/admin/AKScheduling/constraint_violations.html
index 19c769713de5cb8325dee34ed4ade20ace89b6be..26ba07575f2a826349523be5303e125f557accbd 100644
--- a/AKScheduling/templates/admin/AKScheduling/constraint_violations.html
+++ b/AKScheduling/templates/admin/AKScheduling/constraint_violations.html
@@ -13,78 +13,45 @@
 {% block extrahead %}
     {{ block.super }}
 
+    <script type="application/javascript" src="{% static "common/js/api.js" %}"></script>
+    <script type="application/javascript" src="{% static "AKScheduling/js/scheduling.js" %}"></script>
+
     <script>
         document.addEventListener('DOMContentLoaded', function () {
-            // CSRF Protection/Authentication
-            function getCookie(name) {
-                let cookieValue = null;
-                if (document.cookie && document.cookie !== '') {
-                    const cookies = document.cookie.split(';');
-                    for (let i = 0; i < cookies.length; i++) {
-                        const cookie = cookies[i].trim();
-                        // Does this cookie string begin with the name we want?
-                        if (cookie.substring(0, name.length + 1) === (name + '=')) {
-                            cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
-                            break;
-                        }
-                    }
-                }
-                return cookieValue;
+            const url = "{% url "model:scheduling-constraint-violations-list" event_slug=event.slug %}";
+
+            const callback_success = function(response) {
+               let table_html = '';
+
+               if(response.length > 0) {
+                   // Update violation count badge
+                   $('#violationCountBadge').html(response.length).removeClass('badge-success').addClass('badge-warning');
+
+                   // Update violations table
+                   for(let i=0;i<response.length;i++) {
+                       if(response[i].manually_resolved)
+                           table_html += '<tr class="text-muted"><td class="nowrap">{% fa5_icon "check" "fas" %}</td>';
+                       else
+                           table_html += '<tr><td></td>';
+                       table_html += "<td>" + response[i].level_display + "</td><td>" + response[i].type_display + "</td><td>" + response[i].details + "</td><td class='nowrap'>" + response[i].timestamp_display + "</td><td><a href='" + response[i].edit_url + "'><i class='btn btn-primary fa fa-pen'></i></a></td></tr>";
+                   }
+               }
+               else {
+                   // Update violation count badge
+                   $('#violationCountBadge').html(0).removeClass('badge-warning').addClass('badge-success');
+
+                   // Update violations table
+                   table_html ='<tr class="text-muted"><td colspan="5" class="text-center">{% trans "No violations" %}</td></tr>'
+               }
+
+               // Show violation list (potentially empty) in violations table
+               $('#violationsTableBody').html(table_html);
             }
 
-            const csrftoken = getCookie('csrftoken');
-
-            function csrfSafeMethod(method) {
-                // these HTTP methods do not require CSRF protection
-                return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
-            }
-
-            $.ajaxSetup({
-                beforeSend: function (xhr, settings) {
-                    if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
-                        xhr.setRequestHeader("X-CSRFToken", csrftoken);
-                    }
-                }
-            });
 
             // (Re-)Load constraint violations using AJAX and visualize using violation count badge and violation table
             function reload() {
-                $.ajax({
-                       url: "{% url "model:scheduling-constraint-violations-list" event_slug=event.slug %}",
-                        type: 'GET',
-                        success: function (response) {
-                           console.log(response);
-
-                           let table_html = '';
-
-                           if(response.length > 0) {
-                               // Update violation count badge
-                               $('#violationCountBadge').html(response.length).removeClass('badge-success').addClass('badge-warning');
-
-                               // Update violations table
-                               for(let i=0;i<response.length;i++) {
-                                   if(response[i].manually_resolved)
-                                       table_html += '<tr class="text-muted"><td class="nowrap">{% fa5_icon "check" "fas" %}</td>';
-                                   else
-                                       table_html += '<tr><td></td>';
-                                   table_html += "<td>" + response[i].level_display + "</td><td>" + response[i].type_display + "</td><td>" + response[i].details + "</td><td class='nowrap'>" + response[i].timestamp_display + "</td><td><a href='" + response[i].edit_url + "'><i class='btn btn-primary fa fa-pen'></i></a></td></tr>";
-                               }
-                           }
-                           else {
-                               // Update violation count badge
-                               $('#violationCountBadge').html(0).removeClass('badge-warning').addClass('badge-success');
-
-                               // Update violations table
-                               table_html ='<tr class="text-muted"><td colspan="5" class="text-center">{% trans "No violations" %}</td></tr>'
-                           }
-
-                           // Show violation list (potentially empty) in violations table
-                           $('#violationsTableBody').html(table_html);
-                        },
-                        error: function (response) {
-                           alert("{% trans 'Cannot load current violations from server' %}");
-                        }
-                   });
+                loadCVs(url, callback_success, default_cv_callback_error)
             }
             reload();
 
diff --git a/AKScheduling/templates/admin/AKScheduling/scheduling.html b/AKScheduling/templates/admin/AKScheduling/scheduling.html
index 465f422a3c2ac8658cb19b8f0ade88c42a7ac411..50ab859c612dee4605f7ab0cf1566694038476e3 100644
--- a/AKScheduling/templates/admin/AKScheduling/scheduling.html
+++ b/AKScheduling/templates/admin/AKScheduling/scheduling.html
@@ -59,48 +59,17 @@
         }
     </style>
 
+    <script type="application/javascript" src="{% static "common/js/api.js" %}"></script>
+    <script type="application/javascript" src="{% static "AKScheduling/js/scheduling.js" %}"></script>
+
     <script>
         document.addEventListener('DOMContentLoaded', function () {
-            // CSRF Protection/Authentication
-            function getCookie(name) {
-                let cookieValue = null;
-                if (document.cookie && document.cookie !== '') {
-                    const cookies = document.cookie.split(';');
-                    for (let i = 0; i < cookies.length; i++) {
-                        const cookie = cookies[i].trim();
-                        // Does this cookie string begin with the name we want?
-                        if (cookie.substring(0, name.length + 1) === (name + '=')) {
-                            cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
-                            break;
-                        }
-                    }
-                }
-                return cookieValue;
-            }
-
-            const csrftoken = getCookie('csrftoken');
-
-            function csrfSafeMethod(method) {
-                // these HTTP methods do not require CSRF protection
-                return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
-            }
-
-            $.ajaxSetup({
-                beforeSend: function (xhr, settings) {
-                    if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
-                        xhr.setRequestHeader("X-CSRFToken", csrftoken);
-                    }
-                }
-            });
-
-
             // Place slots by dropping placeholders on calendar
             var containerEl = document.getElementById('unscheduled-slots');
             new FullCalendar.Draggable(containerEl, {
                 itemSelector: '.unscheduled-slot',
             });
 
-
             // Calendar
             var planEl = document.getElementById('planCalendar');
 
@@ -211,6 +180,58 @@
             $('.unscheduled-slot').each(function() {
                 $(this).tooltip({title: $(this).first().attr('data-details'), trigger: 'hover'});
             });
+
+            const cv_url = "{% url "model:scheduling-constraint-violations-list" event_slug=event.slug %}";
+
+            const cv_callback_success = function(response) {
+               let table_html = '';
+
+               if(response.length > 0) {
+                   // Update violation count badge
+                   $('#violationCountBadge').html(response.length).removeClass('badge-success').addClass('badge-warning');
+
+                   // Update violations table
+                   for(let i=0;i<response.length;i++) {
+                       if(response[i].manually_resolved)
+                           table_html += '<tr class="text-muted"><td class="nowrap">{% fa5_icon "check" "fas" %} ';
+                       else
+                           table_html += '<tr><td>';
+
+                       if(response[i].level_display==='{% trans "Violation" %}')
+                           table_html += '{% fa5_icon "exclamation-circle" "fas" %}';
+                       else
+                           table_html += '{% fa5_icon "info-circle" "fas" %}';
+
+                       table_html += "</td><td class='small'>" + response[i].type_display + "</td></tr>";
+                       table_html += "<tr><td colspan='2' class='small'>" + response[i].details + "</td></tr>"
+                   }
+               }
+               else {
+                   // Update violation count badge
+                   $('#violationCountBadge').html(0).removeClass('badge-warning').addClass('badge-success');
+
+                   // Update violations table
+                   table_html ='<tr class="text-muted"><td colspan="2" class="text-center">{% trans "No violations" %}</td></tr>'
+               }
+
+               // Show violation list (potentially empty) in violations table
+               $('#violationsTableBody').html(table_html);
+            }
+
+            function reloadCVs() {
+                loadCVs(cv_url, cv_callback_success, default_cv_callback_error);
+            }
+            reloadCVs();
+
+            const reloadBtn = $('#reloadBtn');
+
+            function reload() {
+                plan.refetchEvents();
+                reloadCVs();
+                // TODO Reload unscheduled AKs
+            }
+
+            reloadBtn.click(reload);
         });
     </script>
 
@@ -218,28 +239,65 @@
 <body>
     <div class="box p-3">
       <div class="row header pb-2">
-          <div class="col-sm-10">
-              <h2 class="d-inline">{% trans "Scheduling for" %} {{event}}</h2> <h5 class="d-inline ml-2"><a href="{% url 'admin:event_status' event.slug %}">{% trans "Event Status" %} {% fa5_icon "level-up-alt" "fas" %}</a></h5>
+          <div class="col">
+              <h2 class="d-inline">
+                  <button class="btn btn-outline-warning" id="reloadBtn" style="vertical-align: text-bottom;">
+                      <span id="reloadBtnVisDefault">{% fa5_icon "redo" "fas" %}</span>
+                  </button>
+                  {% trans "Scheduling for" %} {{event}}
+              </h2>
+              <h5 class="d-inline ml-2">
+                  <a href="{% url 'admin:event_status' event.slug %}">{% trans "Event Status" %} {% fa5_icon "level-up-alt" "fas" %}</a>
+              </h5>
           </div>
-          <div class="col-sm-2"></div>
       </div>
       <div class="row content">
-        <div class="col-md-10 col-lg-10">
+        <div class="col-md-8 col-lg-9 col-xl-10">
             <div id="planCalendar"></div>
         </div>
-        <div class="col-md-2 col-lg-2" id="unscheduled-slots">
-        {% regroup slots_unscheduled by ak.track as slots_unscheduled_by_track_list %}
-            {% for track_slots in slots_unscheduled_by_track_list %}
-                {% if track_slots.grouper %}
-                    <h5 class="mt-2">{{ track_slots.grouper }}</h5>
-                {% endif %}
-                {% for slot in track_slots.list %}
-                    <div class="unscheduled-slot badge badge-primary" style='background-color: {{ slot.ak.category.color }}'
-                         data-event='{ "title": "{{ slot.ak.short_name }}", "duration": {"hours": "{{ slot.duration|unlocalize }}"}, "constraint": "roomAvailable", "description": "{{ slot.ak.details | escapejs }}", "slotID": "{{ slot.pk }}", "backgroundColor": "{{ slot.ak.category.color }}"}' data-details="{{ slot.ak.details }}">{{ slot.ak.short_name }}
-                        ({{ slot.duration }} h)<br>{{ slot.ak.owners_list }}
-                    </div>
+        <div class="col-md-4 col-lg-3 col-xl-2" id="sidebar">
+            <ul class="nav nav-tabs">
+              <li class="nav-item">
+                <a class="nav-link active" data-toggle="tab" href="#unscheduled-slots">{% trans "Unscheduled" %}</a>
+              </li>
+              <li class="nav-item">
+                <a class="nav-link" data-toggle="tab" href="#violations"><span id="violationCountBadge" class="badge badge-success">0</span> {% trans "Violation(s)" %}</a>
+              </li>
+            </ul>
+            <div id="sidebarContent" class="tab-content">
+              <div class="tab-pane fade show active" id="unscheduled-slots">
+                {% regroup slots_unscheduled by ak.track as slots_unscheduled_by_track_list %}
+                {% for track_slots in slots_unscheduled_by_track_list %}
+                    {% if track_slots.grouper %}
+                        <h5 class="mt-2">{{ track_slots.grouper }}</h5>
+                    {% endif %}
+                    {% for slot in track_slots.list %}
+                        <div class="unscheduled-slot badge badge-primary" style='background-color: {{ slot.ak.category.color }}'
+                             data-event='{ "title": "{{ slot.ak.short_name }}", "duration": {"hours": "{{ slot.duration|unlocalize }}"}, "constraint": "roomAvailable", "description": "{{ slot.ak.details | escapejs }}", "slotID": "{{ slot.pk }}", "backgroundColor": "{{ slot.ak.category.color }}"}' data-details="{{ slot.ak.details }}">{{ slot.ak.short_name }}
+                            ({{ slot.duration }} h)<br>{{ slot.ak.owners_list }}
+                        </div>
+                    {% endfor %}
                 {% endfor %}
-            {% endfor %}
+              </div>
+              <div class="tab-pane fade" id="violations">
+                  <table class="table table-striped mt-4 mb-4">
+                    <thead>
+                        <tr>
+                            <th>{% trans "Level" %}</th>
+                            <th>{% trans "Problem" %}</th>
+                        </tr>
+                    </thead>
+                    <tbody id="violationsTableBody">
+                        <tr class="text-muted">
+                            <td colspan="2" class="text-center">
+                                {% trans "No violations" %}
+                            </td>
+                        </tr>
+                    </tbody>
+                </table>
+
+              </div>
+            </div>
         </div>
       </div>
       <div class="row footer">
diff --git a/static_common/common/js/api.js b/static_common/common/js/api.js
new file mode 100644
index 0000000000000000000000000000000000000000..ba966fb22f275182c7ab9dc89c54537a73ef0c01
--- /dev/null
+++ b/static_common/common/js/api.js
@@ -0,0 +1,31 @@
+// CSRF Protection/Authentication
+function getCookie(name) {
+    let cookieValue = null;
+    if (document.cookie && document.cookie !== '') {
+        const cookies = document.cookie.split(';');
+        for (let i = 0; i < cookies.length; i++) {
+            const cookie = cookies[i].trim();
+            // Does this cookie string begin with the name we want?
+            if (cookie.substring(0, name.length + 1) === (name + '=')) {
+                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
+                break;
+            }
+        }
+    }
+    return cookieValue;
+}
+
+const csrftoken = getCookie('csrftoken');
+
+function csrfSafeMethod(method) {
+    // these HTTP methods do not require CSRF protection
+    return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
+}
+
+$.ajaxSetup({
+    beforeSend: function (xhr, settings) {
+        if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
+            xhr.setRequestHeader("X-CSRFToken", csrftoken);
+        }
+    }
+});
\ No newline at end of file