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