Browse Source

add slot picker widget

subDesTagesMitExtraKaese 2 years ago
parent
commit
de89c62d9b

+ 5 - 5
locale/de/LC_MESSAGES/django.po

@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2022-12-17 15:48+0000\n"
+"POT-Creation-Date: 2022-12-18 01:25+0000\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"
@@ -20,7 +20,7 @@ msgstr ""
 
 #: zitap/templates/zitap/create-event.html:5
 #: zitap/templates/zitap/event-not-found.html:10
-#: zitap/templates/zitap/index.html:24
+#: zitap/templates/zitap/index.html:25
 msgid "Create Event"
 msgstr ""
 
@@ -28,14 +28,14 @@ msgstr ""
 msgid "Event not found"
 msgstr ""
 
-#: zitap/templates/zitap/event.html:51
+#: zitap/templates/zitap/event.html:45
 msgid "Logout"
 msgstr ""
 
-#: zitap/templates/zitap/index.html:17
+#: zitap/templates/zitap/index.html:18
 msgid "Home"
 msgstr ""
 
-#: zitap/templates/zitap/index.html:18
+#: zitap/templates/zitap/index.html:19
 msgid "About"
 msgstr ""

+ 13 - 4
zitap/forms.py

@@ -2,18 +2,27 @@ import datetime
 
 from django import forms
 
-from .widgets import DatePickerWidget
+from .widgets import DatePickerWidget, SlotPickerWidget
+
+def get_hours():
+    return [(x, datetime.time(x, 0)) for x in range(24)]
+def get_interval():
+    return [(x, datetime.timedelta(minutes=x)) for x in [15, 30, 60]]
 
 class CreateEventForm(forms.Form):
     event_name = forms.CharField(label='Event name', max_length=100)
     event_date = forms.Field(label='Event date', widget=DatePickerWidget)
-    start_time = forms.TimeField(label='Start time', initial=datetime.time(9, 0))
-    end_time = forms.TimeField(label='End time', initial=datetime.time(20, 0))
+    start_time = forms.ChoiceField(label='Start time', initial=9, choices=get_hours)
+    end_time = forms.ChoiceField(label='End time', initial=20, choices=get_hours)
+    slot_interval = forms.ChoiceField(label='Time slot interval', initial=15, choices=get_interval)
 
 class LoginForm(forms.Form):
     username = forms.CharField(label='Username', max_length=100)
     password = forms.CharField(label='Password', max_length=100, widget=forms.PasswordInput, required=False)
 
 class UpdateSlotsForm(forms.Form):
-    slots = forms.CharField(label='Slots', max_length=100)
+    slots = forms.Field(label='Slots')
 
+    def __init__(self, *args, participant, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.fields['slots'].widget = SlotPickerWidget(participant=participant)

+ 1 - 2
zitap/helpers.py

@@ -35,7 +35,7 @@ def get_slot_count( event ) -> int :
     slots_per_day = int(timespan.total_seconds() // event.slot_interval.total_seconds())
     return days * slots_per_day
 
-def slots2grid( event : Event ) -> dict :
+def slots2grid( event : Event, participants : list[Participant]) -> dict :
     """ Convert the slots of an event to data for the grid. """
 
     # Get the number of slots in the event
@@ -56,7 +56,6 @@ def slots2grid( event : Event ) -> dict :
     slots_per_day = int(timespan.total_seconds() // event.slot_interval.total_seconds())
 
     # Get the slots of each day
-    participants = event.participant_set.all()
     participant_slot_strings = [slots2string(participant, n_slots) for participant in participants]
     max_occupancy = 1
 

+ 84 - 0
zitap/static/zitap/css/slot-picker.css

@@ -0,0 +1,84 @@
+.slot-picker{
+    --slot-witdh: 3.5rem;
+    --slot-gap: 1rem;
+    display: flex;
+    user-select: none;
+    width: fit-content;
+    margin: 1rem;
+    padding-left: 4rem;
+    gap: var(--slot-gap);
+    font-size: 1em;
+    flex-wrap: wrap;
+    flex-direction: row-reverse;
+    background-color: white;
+  }
+  .slot-picker .slot-column {
+    display: flex;
+    flex-direction: column;
+    justify-content: space-between;
+    height: 20rem;
+    z-index: 20;
+    background-color: white;
+  }
+  
+  .slot-picker .day {
+    font-weight: bold;
+    align-self: center;
+    justify-self: center;
+    height: 2rem;
+  }
+  .slot-picker .time-label{
+    font-weight: bold;
+    position: absolute;
+    transform: translate(-4rem, -50%);
+    z-index: 1;
+  }
+  .slot-picker .slot {
+    position: relative;
+    width: var(--slot-witdh);
+    flex-grow: 1;
+  }
+  .slot-picker .slot.half-hour label::after {
+    content: " ";
+    border-top: 1px solid #0000002b;
+    position: absolute;
+    width: calc(var(--slot-witdh) + var(--slot-gap) + 3rem + 1px);
+    left: calc(-1 * var(--slot-gap) - 3rem - 1px);
+  }
+  .slot-picker .slot.full-hour label::after {
+    content: " ";
+    border-top: 1px solid #0000007d;
+    position: absolute;
+    width: calc(var(--slot-witdh) + var(--slot-gap) + 1px);
+    left: calc(-1 * var(--slot-gap) - 1px);
+  }
+  .slot-picker .slot:last-child label {
+    border-bottom: 1px solid #0000007d;
+  }
+  .slot-picker .slot:hover {
+    background-color: rgb(214, 93, 93) !important;
+    cursor: pointer;
+  }
+
+.slot-picker .slot input[type=checkbox] {
+    visibility: hidden;
+    position: absolute;
+}
+.slot-picker .slot label {
+    display: block;
+    width: 100%;
+    height: 100%;
+    transition: background-color .2s ease;
+    cursor: pointer;
+    border-left: 1px solid #0000007d;
+    border-right: 1px solid #0000007d;
+}
+.slot-picker .slot label:hover {
+    background-color: #d65d5d;
+}
+.slot-picker .slot input[type=checkbox]:checked + label:hover {
+    background-color: rgb(185, 165, 52);
+}
+.slot-picker .slot input[type=checkbox]:checked + label {
+    background-color: rgb(52, 185, 52);
+}

+ 14 - 0
zitap/templates/widgets/slot-picker.html

@@ -0,0 +1,14 @@
+<div class="slot-picker" id="{{ widget.attrs.id }}">
+  {% for day in widget.grid.days %}
+  <div class="slot-column">
+      <div class="day">{{ day.date|date:"M d" }}</div>
+      {% for slot in day.slots %}
+      <div class="slot {% if slot.is_full_hour %} full-hour{% elif slot.is_half_hour %} half-hour{% endif %}" title="{{ slot.tooltip }}">
+          {% if slot.is_full_hour %}<div class="time-label">{{ slot.time|date:"H:i" }}</div>{% endif %}
+          <input class="checkable" type="checkbox" id="slot_picker_{{ slot.offset }}" name="slot_{{ slot.offset }}" />
+          <label class="checkable" for="slot_picker_{{ slot.offset }}"></label>
+      </div>
+      {% endfor %}
+  </div>
+  {% endfor %}
+</div>

+ 1 - 1
zitap/templates/zitap/create-event.html

@@ -11,7 +11,7 @@
         {% csrf_token %}
         <div class="grid-container">
         {% for field in form %}
-        <div class="{% cycle 'event_name' 'event_dates' 'start_time' 'end_time' %}">
+        <div class="{{ field.name }}">
             <p>
             {{ field.label_tag }}<br>
             {{ field }}

+ 2 - 0
zitap/templates/zitap/event.html

@@ -6,6 +6,7 @@
 
 {% block head %}
     <link rel="stylesheet" href="{% static 'zitap/css/event.css' %}">
+    <link rel="stylesheet" href="{% static 'zitap/css/slot-picker.css' %}">
     <style type="text/css">
         {% for color in grid.colors %}
             .occupancy-grid .slot.{{ color.name }} {
@@ -43,6 +44,7 @@
     <button class="logout" onclick="location.href='{% url 'logout' url=event.url %}'">
         {% trans "Logout" %}
     </button>
+    <script type="text/javascript" src="{% static 'zitap/js/mouse-drag.js' %}"></script>
 {% else %}
     <form action="{% url 'login' url=event.url %}" method="post">
         {% csrf_token %}

+ 31 - 1
zitap/widgets.py

@@ -1,6 +1,8 @@
 import datetime
 
 from django.forms.widgets import Widget
+from .models import Participant, Date, Event
+from .helpers import string2slots, get_slot_count, slots2string, slots2grid
 
 class DatePickerWidget(Widget):
     template_name = 'widgets/date-picker.html'
@@ -60,4 +62,32 @@ class DatePickerWidget(Widget):
         return [
             (today + datetime.timedelta(days=i))
             for i in range(7*self.n_weeks)
-        ]
+        ]
+
+class SlotPickerWidget(Widget):
+    template_name = 'widgets/slot-picker.html'
+
+    def get_context(self, name, value, attrs):
+        context = super().get_context(name, value, attrs)
+        event: Event = self.participant.event
+
+        context['widget']['grid'] = slots2grid(event, [self.participant])
+        return context
+
+    def value_from_datadict(self, data, files, name):
+        slots = []
+        for i in range(self.n_slots):
+            if data.get(f"slot_{i}") == "on":
+                slots.append(i)
+        return slots
+
+    def value_omitted_from_data(self, data, files, name):
+        return not any(
+            (data.get(f"slot_{i}") == "on")
+            for i in range(self.n_slots)
+        )
+    
+    def __init__(self, *args, participant, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.participant = participant
+        self.n_slots = get_slot_count(participant.event)