From 7ccd63a373cd2717177a0066daa2332bcba74882 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Benjamin=20H=C3=A4ttasch?=
 <benjamin.haettasch@fachschaft.informatik.tu-darmstadt.de>
Date: Wed, 28 Dec 2022 01:07:06 +0100
Subject: [PATCH] Introduce alternative way to input availabilities and slots

Add start and end inputs below the calendar widget that can be used to add new entries to the calendar.
Inputs are based on html5 widgets, will fallback to simple text inputs in very old browsers. Modern browsers also provide an integrated validation of the time ranges. In older browsers, this will be done latest on server side.
This implements #124
---
 AKModel/availability/forms.py              |  3 +-
 AKModel/forms.py                           |  3 +-
 AKModel/locale/de_DE/LC_MESSAGES/django.po | 30 ++++++++++++------
 static_common/common/js/availabilities.js  | 36 ++++++++++++++++------
 4 files changed, 51 insertions(+), 21 deletions(-)

diff --git a/AKModel/availability/forms.py b/AKModel/availability/forms.py
index c99ca202..17594263 100644
--- a/AKModel/availability/forms.py
+++ b/AKModel/availability/forms.py
@@ -20,7 +20,8 @@ class AvailabilitiesFormMixin(forms.Form):
     availabilities = forms.CharField(
         label=_('Availability'),
         help_text=_(
-            'Click and drag to mark the availability during the event, double-click to delete.'  # Adapted help text
+            'Click and drag to mark the availability during the event, double-click to delete. '
+            'Or use the start and end inputs to add entries to the calendar view.'  # Adapted help text
         ),
         widget=forms.TextInput(attrs={'class': 'availabilities-editor-data'}),
         required=False,
diff --git a/AKModel/forms.py b/AKModel/forms.py
index c1386538..e52112f6 100644
--- a/AKModel/forms.py
+++ b/AKModel/forms.py
@@ -112,7 +112,8 @@ class DefaultSlotEditorForm(AdminIntermediateForm):
     availabilities = forms.CharField(
         label=_('Default Slots'),
         help_text=_(
-            'Click and drag to mark the availability during the event, double-click to delete.'  # Adapted help text
+            'Click and drag to add default slots, double-click to delete. '
+            'Or use the start and end inputs to add entries to the calendar view.'
         ),
         widget=forms.TextInput(attrs={'class': 'availabilities-editor-data'}),
         required=True,
diff --git a/AKModel/locale/de_DE/LC_MESSAGES/django.po b/AKModel/locale/de_DE/LC_MESSAGES/django.po
index fbc60dcb..f3949e44 100644
--- a/AKModel/locale/de_DE/LC_MESSAGES/django.po
+++ b/AKModel/locale/de_DE/LC_MESSAGES/django.po
@@ -2,7 +2,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2022-12-27 00:33+0100\n"
+"POT-Creation-Date: 2022-12-28 01:03+0100\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -81,23 +81,24 @@ msgstr "Constraintverletzungen auf Level \"Warning\" setzen"
 msgid "Availability"
 msgstr "Verfügbarkeit"
 
-#: AKModel/availability/forms.py:23 AKModel/forms.py:115
+#: AKModel/availability/forms.py:23
 msgid ""
 "Click and drag to mark the availability during the event, double-click to "
-"delete."
+"delete. Or use the start and end inputs to add entries to the calendar view."
 msgstr ""
 "Klicken und ziehen um die Verfügbarkeiten während des Events zu markieren. "
-"Doppelt klicken um Einträge zu löschen."
+"Doppelt klicken um Einträge zu löschen. Oder Start- und End-Eingabe "
+"verwenden, um der Kalenderansicht neue Einträge hinzuzufügen."
 
-#: AKModel/availability/forms.py:87
+#: AKModel/availability/forms.py:88
 msgid "The submitted availability does not comply with the required format."
 msgstr "Die eingetragenen Verfügbarkeit haben nicht das notwendige Format."
 
-#: AKModel/availability/forms.py:100
+#: AKModel/availability/forms.py:101
 msgid "The submitted availability contains an invalid date."
 msgstr "Die eingegebene Verfügbarkeit enthält ein ungültiges Datum."
 
-#: AKModel/availability/forms.py:123 AKModel/availability/forms.py:133
+#: AKModel/availability/forms.py:124 AKModel/availability/forms.py:134
 msgid "Please fill in your availabilities!"
 msgstr "Bitte Verfügbarkeiten eintragen!"
 
@@ -212,11 +213,20 @@ msgstr ""
 msgid "Default Slots"
 msgstr "Standardslots"
 
-#: AKModel/forms.py:124
+#: AKModel/forms.py:115
+msgid ""
+"Click and drag to add default slots, double-click to delete. Or use the "
+"start and end inputs to add entries to the calendar view."
+msgstr ""
+"Klicken und ziehen um Standardslots hinzuzufügen, doppelt klicken um "
+"Einträge zu löschen. Oder Start- und End-Eingabe verwenden, um der "
+"Kalenderansicht neue Einträge hinzuzufügen."
+
+#: AKModel/forms.py:125
 msgid "New rooms"
 msgstr "Neue Räume"
 
-#: AKModel/forms.py:125
+#: AKModel/forms.py:126
 msgid ""
 "Enter room details in CSV format. Required colum is \"name\", optional "
 "colums are \"location\", \"capacity\", and \"url\" for online/hybrid rooms. "
@@ -226,7 +236,7 @@ msgstr ""
 "Spalten sind \"location\", \"capacity\", und \"url\" for Online-/"
 "HybridräumeTrennzeichen: Semikolon"
 
-#: AKModel/forms.py:136
+#: AKModel/forms.py:137
 msgid "CSV must contain a name column"
 msgstr "CSV muss eine name-Spalte enthalten"
 
diff --git a/static_common/common/js/availabilities.js b/static_common/common/js/availabilities.js
index 84376ba5..950cb23a 100644
--- a/static_common/common/js/availabilities.js
+++ b/static_common/common/js/availabilities.js
@@ -16,6 +16,20 @@ function createAvailabilityEditors(timezone, language, startDate, endDate, slotR
         data_field.after(editor);
         data_field.hide();
 
+        // Add inputs to add slots without the need to click and drag
+        let manualSlotAdderSource = "<form id='formManualAdd'><table class='table table-responsive mb-0'><tr>" +
+            "<td style='vertical-align: middle;'><input type='datetime-local' id='inputStart' value='" + startDate + "' min='" + startDate + "' max='" + endDate + "'></td>" +
+            "<td style='vertical-align: middle;'><i class=\"fas fa-long-arrow-alt-right\"></i></td>" +
+            "<td style='vertical-align: middle;'><input type='datetime-local' id='inputEnd' value='" + endDate + "' min='" + startDate + "' max='" + endDate + "'></td>" +
+            "<td><button class='btn btn-primary' type='submit'><i class=\"fas fa-plus\"></i></button></td></tr></table></form>";
+        let manualSlotAdder = $(manualSlotAdderSource);
+        editor.after(manualSlotAdder);
+
+        $('#formManualAdd').submit(function(event) {
+            add($('#inputStart').val(), $('#inputEnd').val());
+            event.preventDefault();
+        });
+
         let editable = !Boolean(data_field.attr("disabled"));
         let data = JSON.parse(data_field.attr("value"));
         let events = data.availabilities.map(function (e) {
@@ -58,15 +72,7 @@ function createAvailabilityEditors(timezone, language, startDate, endDate, slotR
             events: data.availabilities,
             eventBackgroundColor: eventColor,
             select: function (info) {
-                resetDeletionCandidate();
-                plan.addEvent({
-                    title: "",
-                    start: info.start,
-                    end: info.end,
-                    id: 'new' + newEventsCounter
-                })
-                newEventsCounter++;
-                save_events();
+                add(info.start, info.end);
             },
             eventClick: function (info) {
                 if (eventMarkedForDeletion !== undefined && (eventMarkedForDeletion.id === info.event.id)) {
@@ -88,6 +94,18 @@ function createAvailabilityEditors(timezone, language, startDate, endDate, slotR
         });
         plan.render();
 
+        function add(start, end) {
+            resetDeletionCandidate();
+            plan.addEvent({
+                title: "",
+                start: start,
+                end: end,
+                id: 'new' + newEventsCounter
+            })
+            newEventsCounter++;
+            save_events();
+        }
+
         function makeDeletionCandidate(el) {
             el.classList.add("deleteEvent");
             $(el).find(".fc-event-title").html("<i class='fas fa-trash'></i> <i class='fas fa-question'></i>");
-- 
GitLab