Browse Source

add event grid view

subDesTagesMitExtraKaese 1 năm trước cách đây
mục cha
commit
12c966c0d3

+ 41 - 21
zitap/helpers.py

@@ -24,8 +24,8 @@ def get_slot_count( event ) -> int :
     """ Get the number of slots in an event. """
 
     # Get the timespan of the event
-    start_time = datetime.datetime.combine(datetime.date.today(), event.end_time)
-    end_time = datetime.datetime.combine(datetime.date.today(), event.start_time)
+    start_time = datetime.datetime.combine(datetime.date.today(), event.start_time)
+    end_time = datetime.datetime.combine(datetime.date.today(), event.end_time)
     timespan = abs(end_time - start_time)
 
     # Get the number of slots in the event
@@ -40,45 +40,41 @@ def slots2grid( event : Event ) -> dict :
     n_slots = get_slot_count(event)
 
     data = {
-        'days': [],
         'rows': [],
+        'days': [],
+        'slot_height': 30 * (event.slot_interval.total_seconds() / 3600),
+        'n_days': event.date_set.count(),
     }
     # Get the timespan of the event
-    start_time = datetime.datetime.combine(datetime.date.today(), event.end_time)
-    end_time = datetime.datetime.combine(datetime.date.today(), event.start_time)
+    start_time = datetime.datetime.combine(datetime.date.today(), event.start_time)
+    end_time = datetime.datetime.combine(datetime.date.today(), event.end_time)
     timespan = abs(end_time - start_time)
     
     # Get the slots in a day
-    slots_per_day = timespan.total_seconds() // event.slot_interval.total_seconds()
-
-    # Fill the rows of the grid
-    last_hour = None
-    for i in range(slots_per_day):
-        current_time = start_time + i * event.slot_interval
-        data['rows'].append({
-            'time': current_time,
-            'is_full_hour': current_time.hour != last_hour,
-        })
-        last_hour = current_time.hour
+    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 = 0
+    max_occupancy = 1
 
-    for date in event.date_set.all():
+    for n, date in enumerate(event.date_set.all()):
         # Get participants for each slot
         slot_participants = [[] for i in range(slots_per_day)]
         for i, participant in enumerate(participants):
             for j in range(slots_per_day):
-                if participant_slot_strings[i][j] == '1':
+                if participant_slot_strings[i][n*slots_per_day + j] == '1':
                     slot_participants[j].append(participant)
         # Fill the slots of the day
         slots = []
-        for ps in slot_participants:
+        for j, ps in enumerate(slot_participants):
+            slot_begin = (start_time + j * event.slot_interval).strftime('%H:%M')
+            slot_end = (start_time + (j+1) * event.slot_interval).strftime('%H:%M')
             slots.append({
-                'tooltip': ', '.join([p.user.username for p in ps]),
+                'tooltip': f"{slot_begin} - {slot_end} \n{', '.join([p.user.username for p in ps])}",
                 'occupancy': len(ps),
+                'offset': n*slots_per_day + j,
+                'date': date.date,
             })
             max_occupancy = max(max_occupancy, len(ps))
         
@@ -87,3 +83,27 @@ def slots2grid( event : Event ) -> dict :
             'slots': slots,
         }
         data['days'].append(day)
+
+    # Fill the rows of the grid
+    last_hour = None
+    for i in range(slots_per_day):
+        current_time = start_time + i * event.slot_interval
+        data['rows'].append({
+            'days': [day['slots'][i] for day in data['days']],
+            'time': current_time,
+            'is_full_hour': current_time.hour != last_hour,
+        })
+        last_hour = current_time.hour
+    data['rows'].append({
+        'days': [None for day in data['days']],
+        'time': end_time,
+        'is_full_hour': True,
+    })
+    data['max_value'] = max_occupancy
+
+    # create a color for each slot from white to dark green
+    for row in data['rows']:
+        for day in row['days']:
+            if day is not None:
+                day['color'] = f"hsl(120, 100%, {100 - 70 * day['occupancy'] / max_occupancy}%)"
+    return data

+ 0 - 1
zitap/static/zitap/css/date-picker.css

@@ -3,7 +3,6 @@
     grid-template-columns: repeat(9, 1fr);
     grid-template-rows: repeat(6, 1fr);
     grid-gap: 1px;
-    width: 8mm;
     justify-items: center;
     align-items: center;
     user-select: none;

+ 41 - 0
zitap/static/zitap/css/event.css

@@ -0,0 +1,41 @@
+.occupancy-grid {
+  display: grid;
+  user-select: none;
+  width: fit-content;
+  margin: 10px;
+}
+.occupancy-grid .day {
+  font-weight: bold;
+  align-self: center;
+  justify-self: center;
+}
+.occupancy-grid .time {
+  position: relative;
+  width: 50px;
+}
+.occupancy-grid .time>.time-label{
+  position: absolute;
+  font-weight: bold;
+  transform: translate(0, -50%);
+}
+.occupancy-grid .slot {
+  width: 70px;
+  background-color: #fff;
+  transition: background-color .2s ease;
+  display: grid;
+  justify-items: center;
+  align-items: center;
+  border-left: 1px solid #000;
+  border-right: 1px solid #000;
+  margin: 0 5px;
+}
+.occupancy-grid .slot.full-hour {
+  border-top: 1px solid #000;
+}
+.occupancy-grid .slot.last {
+  border-bottom: 1px solid #000;
+}
+.occupancy-grid .slot:hover {
+  background-color: rgb(214, 93, 93) !important;
+  cursor: pointer;
+}

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

@@ -4,10 +4,39 @@
 
 {% block title %}{{ event.name }}{% endblock %}
 
+{% block head %}
+    <link rel="stylesheet" href="{% static 'zitap/css/event.css' %}">
+    <style type="text/css">
+        .occupancy-grid {
+            grid-template-columns: auto repeat({{ grid.n_days }}, 1fr);
+        }
+        .occupancy-grid .slot {
+            height: {{ grid.slot_height }}px;
+        }
+    </style>
+{% endblock %}
+
 {% block content %}
 <h1>{{ event.name }}</h1>
 
-
+<div class="occupancy-grid">
+    <div></div>
+    {% for day in grid.days %}
+        <div class="day">{{ day.date|date:"M d" }}</div>
+    {% endfor %}
+    {% for row in grid.rows %}
+        <div class="time">
+            {% if row.is_full_hour %}<div class="time-label">{{ row.time|date:"H:i" }}</div>{% endif %}
+        </div>
+        {% for day in row.days %}
+            {% if day %}
+            <div class="slot{% if row.is_full_hour %} full-hour{% endif %}" id="grid_slot_{{ day.offset }}" title="{{ day.tooltip }}" style="background-color: {{ day.color }};"></div>
+            {% else %}
+            <div class="slot last"></div>
+            {% endif %}
+        {% endfor %}
+    {% endfor %}
+</div>
 
 
 {% if update_form %}

+ 1 - 0
zitap/templates/zitap/index.html

@@ -7,6 +7,7 @@
     <meta name="viewport" content="width=device-width,initial-scale=1.0">
     <link rel="stylesheet" href="{% static 'zitap/css/style.css' %}">
     <link rel="stylesheet" href="{% static 'zitap/css/date-picker.css' %}">
+    {% block head %}{% endblock %}
   </head>
   <body>
     <div class="container">

+ 3 - 5
zitap/views.py

@@ -1,4 +1,4 @@
-import datetime
+import random
 from django.db import IntegrityError
 from django.http import HttpResponseNotFound, HttpResponseRedirect, JsonResponse, HttpResponseNotAllowed
 from django.shortcuts import render
@@ -44,7 +44,6 @@ def event(request, url):
     else:
         login_form = LoginForm()
         update_form = None
-
     return render(
         request, 
         'zitap/event.html', 
@@ -77,6 +76,8 @@ def login(request, url):
 
         auth_login(request, user)
         participant, created = Participant.objects.get_or_create(event=event, user=user)
+        participant.slots = random.getrandbits(get_slot_count(event)).to_bytes(get_slot_count(event) // 8, 'big')
+        participant.save()
         request.session['participant_id'] = participant.id
         return HttpResponseRedirect(f'/{event.url}')
 
@@ -85,9 +86,6 @@ def logout(request, url):
         event = Event.objects.get(url=url)
     except Event.DoesNotExist:
         return render(request, 'zitap/event-not-found.html')
-    
-    if request.method != 'POST':
-        return HttpResponseNotAllowed(['POST'])
 
     if 'participant_id' in request.session:
         del request.session['participant_id']