From 4582cd2b9d199b0d7ea6c8b2cad11207f7fca574 Mon Sep 17 00:00:00 2001 From: Piotr Gawron <piotr.gawron@uni.lu> Date: Wed, 6 Sep 2017 10:49:06 +0200 Subject: [PATCH] info about availabilities and holidays for assigning workers --- smash/web/api_urls.py | 1 + smash/web/api_views/daily_planning.py | 62 ++++++++++++++++ smash/web/static/css/appointment.css | 19 +++++ smash/web/static/js/appointment.js | 85 ++++++++++++++++++++++ smash/web/static/js/daily_planning.js | 18 +++-- smash/web/templates/appointments/add.html | 4 - smash/web/templates/appointments/edit.html | 9 +-- 7 files changed, 181 insertions(+), 17 deletions(-) create mode 100644 smash/web/static/css/appointment.css diff --git a/smash/web/api_urls.py b/smash/web/api_urls.py index a53d92e8..aafb4491 100644 --- a/smash/web/api_urls.py +++ b/smash/web/api_urls.py @@ -47,6 +47,7 @@ urlpatterns = [ # daily planning events url(r'^events/(?P<date>\d{4}-\d{2}-\d{2})/$', daily_planning.events, name='web.api.events'), + url(r'^availabilities/(?P<date>\d{4}-\d{2}-\d{2})/$', daily_planning.availabilities, name='web.api.availabilities'), url(r'^events_persist$', daily_planning.events_persist, name='web.api.events_persist'), ] diff --git a/smash/web/api_views/daily_planning.py b/smash/web/api_views/daily_planning.py index e6417df2..d1fbfbc0 100644 --- a/smash/web/api_views/daily_planning.py +++ b/smash/web/api_views/daily_planning.py @@ -132,6 +132,49 @@ def get_availabilities(worker, date): return result +def get_conflicts(worker, date): + result = [] + + appointments = Appointment.objects.filter( + datetime_when__date=date, + location__in=get_filter_locations(worker), + status=Appointment.APPOINTMENT_STATUS_SCHEDULED, + visit__isnull=False).all() + for i, appointment in enumerate(appointments): + if appointment.worker_assigned is not None: + link_when = appointment.datetime_when + link_when = link_when.replace(tzinfo=None) + link_end = link_when + datetime.timedelta(minutes=appointment.length) + event = { + 'title': appointment.title(), + 'duration': appointment.length, + 'appointment_id': appointment.id, + 'link_when': link_when, + 'link_who': appointment.worker_assigned.id, + 'link_end': link_end, + 'location': str(appointment.location), + } + result.append(event) + + links = AppointmentTypeLink.objects.filter(appointment=appointment).all() + for j, link in enumerate(links): + link_when = link.date_when + if link_when is not None: + link_when = link_when.replace(tzinfo=None) + link_end = link_when + datetime.timedelta(minutes=link.appointment_type.default_duration) + event = { + 'title': link.appointment_type.description, + 'duration': build_duration(link.appointment_type.default_duration), + 'appointment_id': appointment.id, + 'link_when': link_when, + 'link_who': link.worker_id, + 'link_end': link_end, + 'location': str(appointment.location), + } + result.append(event) + return result + + @login_required def events(request, date): appointments = Appointment.objects.filter( @@ -192,6 +235,25 @@ def events(request, date): }) +def availabilities(request, date): + availabilities = [] + holidays = [] + conflicts = [] + workers = Worker.objects.filter(locations__in=get_filter_locations(request.user)).exclude( + role=Worker.ROLE_CHOICES_SECRETARY).distinct() + + for worker in workers: + availabilities = availabilities + get_availabilities(worker, date) + holidays = holidays + get_holidays(worker, date) + conflicts = conflicts + get_conflicts(worker, date) + + return JsonResponse({ + "conflicts": conflicts, + 'availabilities': availabilities, + 'holidays': holidays + }) + + @login_required def events_persist(request): event_links = json.loads(request.POST.get('events_to_persist')) diff --git a/smash/web/static/css/appointment.css b/smash/web/static/css/appointment.css new file mode 100644 index 00000000..3fb8456b --- /dev/null +++ b/smash/web/static/css/appointment.css @@ -0,0 +1,19 @@ +.worker-option-holiday { + background-color: #FF0000; +} + +.worker-option-conflict { + background-color: #ff851b; +} + +.worker-option-match { + background-color: #00FF00; +} + +.worker-option-partial-match { + background-color: #FFFF00; +} + +.worker-option-no-match { + background-color: #FFFFFF; +} diff --git a/smash/web/static/js/appointment.js b/smash/web/static/js/appointment.js index a9799661..8d9b5e83 100644 --- a/smash/web/static/js/appointment.js +++ b/smash/web/static/js/appointment.js @@ -108,6 +108,91 @@ function appointment_flying_team_place_behaviour(flying_team_select, location_se }); } + +function appointment_date_change_behaviour(datetime_picker, worker_select, minutes_input, appointment_id) { + function match_availability(id, availabilities, partialMatch, date_start, date_end) { + if (id === undefined) { + return false; + } + id = parseInt(id); + for (var i = 0; i < availabilities.length; i++) { + var availability = availabilities[i]; + if (availability.link_who === id && appointment_id !== availability.appointment_id) { + var event_start = Date.parse(availability.link_when); + var event_end = Date.parse(availability.link_end); + if (partialMatch && event_start >= date_start && event_start <= date_end) { + return true; + } + if (partialMatch && event_end >= date_start && event_end <= date_end) { + return true; + } + if (partialMatch && date_start >= event_start && date_start <= event_end) { + return true; + } + if (event_start <= date_start && date_end <= event_end) { + return true; + } + } + } + return false; + } + + var getSelectedClass = function () { + return $(worker_select).find(":selected")[0].className; + }; + + var previousClass = getSelectedClass(); + + $(worker_select).addClass(previousClass); + + $(worker_select).on('focus', function () { + // Store the current value on focus and on change + previousClass = getSelectedClass(); + }).change(function () { + // Do something with the previous value after the change + + $(worker_select).removeClass(previousClass); + // Make sure the previous value is updated + previousClass = getSelectedClass(); + $(worker_select).addClass(previousClass); + }); + + $(datetime_picker).on("dp.change", function () { + if ($(this).val() != "") { + var datetime_start = Date.parse($(this).val()); + var datetime_end = datetime_start + 60 * 1000 * parseInt(minutes_input.val()); + var date = $(this).val().substr(0, 10); + $.get('/api/availabilities/' + date, function (data) { + var options = $("option", worker_select); + for (var i = 0; i < options.length; i++) { + var option = options[i]; + if (match_availability(option.value, data.holidays, true, datetime_start, datetime_end)) { + console.log(data.holidays); + console.log("HOLIDAY"); + option.className = "worker-option-holiday"; + } else if (match_availability(option.value, data.conflicts, true, datetime_start, datetime_end)) { + console.log(data.conflicts); + console.log("CONFLICT"); + option.className = "worker-option-conflict"; + } else if (match_availability(option.value, data.availabilities, false, datetime_start, datetime_end)) { + console.log(data.availabilities); + console.log("MATCH"); + option.className = "worker-option-match"; + } else if (match_availability(option.value, data.availabilities, true, datetime_start, datetime_end)) { + console.log(data.availabilities); + console.log("PARTIAL_MATCH"); + option.className = "worker-option-partial-match"; + } else { + option.className = "worker-option-no-match"; + } + } + $(worker_select).trigger("change"); + }); + } + }); + +} + function get_calendar_events_function(source, allow_url_redirection) { if (allow_url_redirection === undefined) { allow_url_redirection = false; diff --git a/smash/web/static/js/daily_planning.js b/smash/web/static/js/daily_planning.js index 68f6b33b..4b608d6f 100644 --- a/smash/web/static/js/daily_planning.js +++ b/smash/web/static/js/daily_planning.js @@ -188,14 +188,16 @@ $(document).ready(function () { var saveButton = $(".fc-save-button"); var currentBorder = saveButton.css('border-color'); $.each(calendarEvents, function (i, calendar_event) { - eventsToPersist.push({ - 'link_id': calendar_event.link_id, - 'link_who': parseInt(calendar_event.resourceId), - 'start': calendar_event.start.format() - }); - var index = eventsCleared.indexOf(calendar_event.link_id); - if (index > -1) { - eventsCleared.splice(index, 1); + if (calendar_event.rendering!=="background") { + eventsToPersist.push({ + 'link_id': calendar_event.link_id, + 'link_who': parseInt(calendar_event.resourceId), + 'start': calendar_event.start.format() + }); + var index = eventsCleared.indexOf(calendar_event.link_id); + if (index > -1) { + eventsCleared.splice(index, 1); + } } }); $.post({ diff --git a/smash/web/templates/appointments/add.html b/smash/web/templates/appointments/add.html index 707facd6..e54ad96b 100644 --- a/smash/web/templates/appointments/add.html +++ b/smash/web/templates/appointments/add.html @@ -32,10 +32,6 @@ <a href="{% url 'web.views.appointments' %}" class="btn btn-block btn-default">Cancel</a> </div> - {% comment %} <div class="box-header with-border"> - <h3 class="box-title">Adding an appointment</h3> - </div>{% endcomment %} - <form method="post" action="" class="form-horizontal"> {% csrf_token %} <div class="box-body"> diff --git a/smash/web/templates/appointments/edit.html b/smash/web/templates/appointments/edit.html index 2c1c323f..54a95306 100644 --- a/smash/web/templates/appointments/edit.html +++ b/smash/web/templates/appointments/edit.html @@ -8,6 +8,7 @@ <link rel="stylesheet" href="{% static 'AdminLTE/plugins/datatables/dataTables.bootstrap.css' %}"> {% include "includes/datetimepicker.css.html" %} + <link rel="stylesheet" href="{% static 'css/appointment.css' %}"> {% endblock styles %} {% block ui_active_tab %}'appointments'{% endblock ui_active_tab %} @@ -24,7 +25,7 @@ {% block content %} <div class="row"> - + {% if appointment.visit %} <p class="col-md-2 pull-right"> <a href="{% url 'web.views.visit_details' appointment.visit.id %}" type="button" @@ -34,10 +35,6 @@ </div> <div class="box box-info"> - {% comment %} <div class="box-header with-border"> - <h3 class="box-title">Details of appointment</h3> - </div>{% endcomment %} - <form method="post" action="" class="form-horizontal"> {% csrf_token %} <fieldset> @@ -134,6 +131,8 @@ }); appointment_type_behaviour($("input[name='appointment-appointment_types']"), $("input[name='appointment-length']"), "{% url 'web.api.appointment_types' %}"); appointment_flying_team_place_behaviour($("select[name='appointment-flying_team']"), $("select[name='appointment-location']")); + appointment_date_change_behaviour($("input[name='appointment-datetime_when']"), $("select[name='appointment-worker_assigned']"), $("input[name='appointment-length']"), {{ appointment.id }}); + </script> {% include "includes/datetimepicker.js.html" %} -- GitLab