Skip to content

Commit

Permalink
Calendar day view (#1221)
Browse files Browse the repository at this point in the history
* draft calendar day view

* build barebones layout engine

* color communication and visual cues

* highlight link target shift boxes

* correctly handle calendar view switches

* show date on day view

* add previous and next buttons in day view

* also include shifts from the next day that start before 3am

* correctly handle csp

* improve styling according to requests on #1221

* implement @felixrindt's color suggestion

* suggestions for visual improvement

* implement most @felixrindt's change requests

* correctly call filter

* small fixes from review to day view

* remove unrelated missing migration

* move different modes to their own files

* beautify event headers in event list day view

* remember last date the day view pointed to

* remove duplicate date when no events are to be displayed in the date view

* repair event day view filter, change event header

* add tests for signup and filter

* lint

* restyle event head

---------

Co-authored-by: Julian B <jeriox@users.noreply.github.com>
Co-authored-by: Ben Bals <benbals@posteo.eu>
Co-authored-by: Felix Rindt <felix@rindt.me>
  • Loading branch information
4 people committed Mar 24, 2024
1 parent b60ff4d commit 77807a6
Show file tree
Hide file tree
Showing 12 changed files with 654 additions and 246 deletions.
14 changes: 11 additions & 3 deletions ephios/core/calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@
class ShiftCalendar(HTMLCalendar):
cssclass_month = "table table-fixed"

def __init__(self, shifts, *args, **kwargs):
def __init__(self, shifts, request, *args, **kwargs):
super().__init__(*args, **kwargs)
self.shifts = {
k: list(v) for (k, v) in groupby(shifts, lambda shift: shift.start_time.date().day)
}
self.request = request

def formatmonth(self, theyear, themonth, withyear=True):
self.year, self.month = theyear, themonth
Expand All @@ -30,12 +31,19 @@ def formatweekday(self, day):
def formatday(self, day, weekday):
if day != 0:
cssclass = self.cssclasses[weekday]
today = date.today() == date(self.year, self.month, day)
this_date = date(self.year, self.month, day)
today = date.today() == this_date
if day in self.shifts:
cssclass += " filled"
content = render_to_string(
"core/fragments/calendar_day.html",
{"day": day, "shifts": self.shifts.get(day, None), "today": today},
{
"day": day,
"shifts": self.shifts.get(day, None),
"today": today,
"date": this_date.isoformat(),
"request": self.request,
},
)
return self.day_cell(cssclass, content)
return self.day_cell("noday", "&nbsp;")
Expand Down
220 changes: 66 additions & 154 deletions ephios/core/templates/core/event_list.html
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
{% extends "base.html" %}
{% load utils %}
{% load ephios_crispy %}
{% load crispy_forms_filters %}
{% load cache %}
{% load static %}
{% load i18n %}
{% load event_extras %}
{% load utils %}
{% load humanize %}
{% block title %}
{% translate "Events" %}
{% endblock %}
Expand Down Expand Up @@ -33,9 +34,14 @@ <h1>{% translate "Events" %}
href="?{% param_replace mode="list" date=None %}"
{% endif %}
><span class="fa fa-list"></span></a>

<a class="btn btn-sm {% if mode == "calendar" %}btn-secondary disabled{% else %}btn-outline-secondary{% endif %}"
href="?{% param_replace mode="calendar" %}"
><i class="fa fa-calendar"></i></a>
><span class="fa fa-calendar-alt"></span></a>

<a class="btn btn-sm {% if mode == "day" %}btn-secondary disabled{% else %}btn-outline-secondary{% endif %}"
href="?{% param_replace mode="day" %}"
><span class="fa fa-calendar-day"></span></a>
</div>
</small>
</h1>
Expand Down Expand Up @@ -72,165 +78,71 @@ <h1>{% translate "Events" %}
</div>
</div>
</form>
{% if mode == "calendar" %}
<div class="d-flex order-1 gap-3 mb-3">
<a class="btn btn-secondary"
href="?{% param_replace date=prev_month_first|date:"Y-m-d" %}"
><i class="fas fa-arrow-left"></i> {{ prev_month_first|date:"F Y" }}</a>
{% if perms.core.add_event %}
<div class="dropdown">
<button class="btn btn-primary dropdown-toggle d-none d-md-inline-block" type="button"
id="eventCreateButton"
data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="fa fa-plus"></span>
{% translate "Add event" %}
</button>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="eventCreateButton">
{% for eventtype in eventtypes %}
<a class="dropdown-item" href="{% url "core:event_create" eventtype.pk %}">
{% blocktranslate trimmed with title=eventtype.title %}
Add {{ title }}
{% endblocktranslate %}</a>
{% endfor %}
</div>
{% if mode == "calendar" or mode == "day" %}
{% if perms.core.add_event %}
<div class="dropdown">
<button class="btn btn-primary btn-sm dropdown-toggle d-none d-md-inline-block" type="button"
id="eventCreateButton"
data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="fa fa-plus"></span>
{% translate "Add event" %}
</button>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="eventCreateButton">
{% for eventtype in eventtypes %}
<a class="dropdown-item" href="{% url "core:event_create" eventtype.pk %}">
{% blocktranslate trimmed with title=eventtype.title %}
Add {{ title }}
{% endblocktranslate %}</a>
{% endfor %}
</div>
{% endif %}
<a class="btn btn-secondary ms-auto"
href="?{% param_replace date=next_month_first|date:"Y-m-d" %}"
>{{ next_month_first|date:"F Y" }} <i class="fas fa-arrow-right"></i></a>
</div>
{% endif %}

<div class="d-flex justify-content-between mt-2">
<a class="btn btn-secondary"
href="?{% param_replace date=previous_date|date:"Y-m-d" %}"
><i class="fas fa-arrow-left"></i>
{% if mode == "calendar" %}
{{ previous_date|date:"F Y" }}
{% elif mode == "day" %}
{{ previous_date|date:"d. M" }}
{% endif %}
</a>

<h3 class="mx-3">
{% if mode == "calendar" %}
{{ date|date:"F Y" }}
{% elif mode == "day" %}
{{ date|date:"l, j. F Y" }}
{% endif %}
</h3>

<a class="btn btn-secondary"
href="?{% param_replace date=next_date|date:"Y-m-d" %}"
>
{% if mode == "calendar" %}
{{ next_date|date:"F Y" }}
{% elif mode == "day" %}
{{ next_date|date:"d. M" }}
{% endif %}
<i class="fas fa-arrow-right"></i></a>
</div>
{% endif %}


</div>

{% if mode == "calendar" %}
{{ calendar }}
{% elif not event_list %}
<div class="mb-3">
<h5 class="mt-5 text-center">
{% translate "No results." %}
</h5>
</div>
{% elif mode == "day" %}
{% include "core/fragments/event_list_day_mode.html" %}
{% else %}
<form id="bulk_action_form" method="post">
{% csrf_token %}
{% if perms.core.add_event %}
<div id="event-controls" class="d-flex flex-wrap pb-3">
<div class="dropdown m-1 ms-0">
<button class="btn btn-primary btn-sm dropdown-toggle" type="button" id="eventCreateButton"
data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="fa fa-plus"></span>
{% translate "Add event" %}
</button>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="eventCreateButton">
{% for eventtype in eventtypes %}
<a class="dropdown-item" href="{% url "core:event_create" eventtype.pk %}">
{% blocktranslate trimmed with title=eventtype.title %}
Add {{ title }}
{% endblocktranslate %}</a>
{% endfor %}
</div>
</div>
<div class="bulk-actions">
<button class="btn btn-secondary btn-sm m-1 ms-0" type="submit" name="delete"
formaction="{% url "core:event_bulk_delete" %}"><span
class="fa fa-trash-alt"></span> {% translate "Delete selected" %}</button>
{% event_bulk_actions %}
</div>
</div>
{% endif %}

{% if event_list %}
<ul class="list-group mb-3">
{% if perms.core.add_event %}
<li class="list-group-item px-0">
<div class="ps-2">
<input type="checkbox" id="checkall">
</div>
</li>
{% endif %}
{% for event in event_list %}
{% with counter=event|event_list_signup_state_counts stats=event.get_signup_stats %}
<li class="list-group-item p-0 d-flex flex-row">
<div class="m-0 py-2 d-flex flex-column flex-lg-row-reverse justify-content-around flex-grow-0">
<div class="ps-lg-2 d-flex flex-row flex-lg-column justify-content-center event-list-status-icon"
{% if counter %}
data-bs-toggle="tooltip" data-bs-placement="bottom" data-bs-html="true"
title="{% for state in counter %}<div>
{% if event.shifts.all|length > 1 %}{{ counter|get:state }} {% endif %}{{ ParticipationStates.labels_dict|get:state }}</div>{% endfor %}"
{% endif %}>
{% if counter|get:ParticipationStates.CONFIRMED > 0 %}
<span class="text-success fa fa-user-check ps-2"></span>
{% elif counter|get:ParticipationStates.REQUESTED > 0 %}
<span class="text-warning fa fa-user-clock ps-2"></span>
{% elif counter|get:ParticipationStates.RESPONSIBLE_REJECTED > 0 %}
<span class="text-danger fa fa-user-times ps-2"></span>
{% elif counter|get:ParticipationStates.USER_DECLINED > 0 and counter|length == 1 %}
<span class="text-danger fa fa-user-minus ps-2"></span>
{% else %}
<span class="text-body-secondary far fa-user ps-2"></span>
{% endif %}
</div>
{% if perms.core.add_event %}
<div class="ps-lg-2 d-flex flex-row flex-lg-column justify-content-center">
<input name="bulk_action" value="{{ event.pk }}" type="checkbox"
class="cb-element">
</div>
{% endif %}
</div>
<a class="w-100 py-2 text-reset event-list-item-link"
href="{{ event.get_absolute_url }}">
<div class="grid-wrapper m-0 py-0 ps-2 pe-3">
<div class="grid-title">
<h5 class="mb-0 text-break">
{{ event.title }}
</h5>
<span class="w-100 text-body-secondary text-break">
{{ event.location }}
</span>
</div>
<div class="grid-batch"><span
class="badge eventtype-{{ event.type.pk }}-color">{{ event.type }}</span>
</div>
<div class="grid-signup d-flex flex-column align-items-end align-items-lg-center justify-content-center">
<div class="position-relative">
<span class="fas fa-users"></span>
{% if event.pending_disposition_count %}
<span class="pending-indicator"></span>
{% endif %}
{{ stats.confirmed_count }}
</div>
<div>
{% include "core/fragments/signup_stats_indicator.html" with stats=stats %}
</div>
</div>
<div class="grid-time">
{{ event.start_time|date:"D" }},
{% if event.start_time.date == event.end_time.date %}
{{ event.start_time|date:"SHORT_DATE_FORMAT" }}
<span class="d-lg-none">,</span>
<span class="d-none d-lg-inline"><br></span>
{{ event.start_time|date:"TIME_FORMAT" }} –
{{ event.end_time|date:"TIME_FORMAT" }}
{% else %}
{{ event.start_time|date:"SHORT_DATE_FORMAT" }}
<span class="d-none d-lg-inline"><br></span>
{% translate "to" %}
{{ event.end_time|date:"SHORT_DATE_FORMAT" }}
{% endif %}
</div>
<div class="grid-action d-none d-lg-flex flex-column justify-content-center">
<div class="btn btn-outline-primary text-nowrap event-list-item-button">
<span class="fa fa-eye"></span> {% translate "Show" %}
</div>
</div>
</div>
</a>
</li>
{% endwith %}
{% endfor %}
</ul>
{% include 'core/pagination.html' %}
{% else %}
<div class="mb-3">
<h5 class="mt-5 text-center">
{% translate "No results." %}
</h5>
</div>
{% endif %}
</form>
{% include "core/fragments/event_list_list_mode.html" %}
{% endif %}
{% endblock %}
20 changes: 16 additions & 4 deletions ephios/core/templates/core/fragments/calendar_day.html
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
{% load i18n %}
{% load utils %}

<div class="text-center p-1">
<span {% if today %}class="badge bg-primary rounded-circle fs-6"{% endif %}>{{ day }}</span>
<span {% if today %}class="badge bg-primary rounded-circle fs-6"{% endif %}>
<a href="?{% param_replace mode="day" date=date %}" class="text-decoration-none {% if today %} link-light text {% endif %}">
{{ day }}
</a>
</span>
</div>
<div>
{% for shift in shifts %}
<a href="{{ shift.event.get_absolute_url }}" class="text-decoration-none p-0" title="{{ shift.event.type }}: {{ shift.event.title }}">
{% for shift in shifts|slice:":5" %}
<a href="{{ shift.event.get_absolute_url }}" class="text-decoration-none p-0"
title="{{ shift.event.type }}: {{ shift.event.title }}">
<div class="w-100 d-inline-block calendar-truncate calendar-shift eventtype-{{ shift.event.type.pk }}-color rounded">
<span class="eventtype-indicator eventtype-{{ shift.event.type.pk }}-color"></span>
<small class="d-none d-lg-inline-block">{{ shift.start_time|time }}</small> <span class="ps-1 ps-lg-0">{{ shift.event.title }}</span>
<small class="d-none d-lg-inline-block">{{ shift.start_time|time }}</small> <span
class="ps-1 ps-lg-0">{{ shift.event.title }}</span>
</div>
</a>
{% endfor %}
{% if shifts|length > 5 %}
<a href="?{% param_replace mode="day" date=date %}">{{ shifts|length|add:"-5" }} {% translate 'more' %}</a>
{% endif %}
</div>
Loading

0 comments on commit 77807a6

Please sign in to comment.