Skip to content

Commit 64f71d8

Browse files
committed
Add day view with shift type columns
1 parent 857a973 commit 64f71d8

14 files changed

Lines changed: 242 additions & 28 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = 'shiftings'
3-
version = '0.1.0'
3+
version = '0.2.0'
44
description = 'Simple shift management system'
55
readme = 'README.md'
66
requires-python = '>=3.9'

src/shiftings/cal/static/css/calendar.css

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,4 +200,23 @@ a.calendar-shift > a:hover {
200200
.shift-type a:hover {
201201
background-color: rgba(var(--bs-dark-rgb), .5);
202202
padding: .1rem .25rem;
203-
}
203+
}
204+
205+
.shift-type-table {
206+
display: grid;
207+
grid-auto-rows: 150px;
208+
}
209+
210+
.shift-type-table > div {
211+
display: flex;
212+
width: 100%;
213+
height: 100%;
214+
flex-flow: row nowrap;
215+
}
216+
217+
.shift-type-table > div > div {
218+
display: flex;
219+
flex-grow: 4;
220+
align-items: center;
221+
justify-content: center;
222+
}

src/shiftings/cal/templates/cal/calendar_base.html

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,28 @@
99
<div class="card base-card bg-dark p-3">
1010
<div class="row">
1111
<div class="col-md-12 d-flex flex-column">
12-
<a class="btn btn-success mb-1" title="{% trans "Day View" %}" href="{% url "overview_today" %}">
13-
<i class="fa-solid fa-calendar-day me-1"></i>{% trans "Day" %}
14-
</a>
12+
<div class="row g-1">
13+
<div class="col">
14+
<a class="btn btn-success mb-1 w-100" title="{% trans "Day View" %}"
15+
{% if theday %}
16+
href="{% url "overview_day" theday %}"
17+
{% else %}
18+
href="{% url "overview_today" %}"
19+
{% endif %}>
20+
<i class="fa-solid fa-calendar-day me-1"></i>{% trans "Day (Detailed)" %}
21+
</a>
22+
</div>
23+
<div class="col">
24+
<a class="btn btn-success mb-1 w-100" title="{% trans "Day View" %}"
25+
{% if theday %}
26+
href="{% url "overview_day_shift_types" theday %}"
27+
{% else %}
28+
href="{% url "overview_today_shift_types" %}"
29+
{% endif %}>
30+
<i class="fa-solid fa-calendar-day me-1"></i>{% trans "Day (By&nbsp;Types)" %}
31+
</a>
32+
</div>
33+
</div>
1534
{# <a class="btn btn-secondary mb-1 disabled" title="{% trans "Week View" %}" href="{% url "overview_thisweek" %}">#}
1635
{# <i class="fa-solid fa-calendar-week me-2"></i>{% trans "Week" %}#}
1736
{# </a>#}
@@ -21,8 +40,10 @@
2140
</div>
2241
<div class="col-4 col-md-12 mb-2">
2342
<h4>{% trans "Filters" %}:</h4>
24-
<a class="btn btn-secondary mb-2 {% active_param 'filter' 'all' %}" href="?filter=all">{% trans "All Shifts" %}</a>
25-
<a class="btn btn-secondary mb-2 {% active_param 'filter' 'own' %}" href="?filter=own">{% trans "My Shifts" %}</a>
43+
<a class="btn btn-secondary mb-2 {% active_param 'filter' 'all' %}"
44+
href="?filter=all">{% trans "All Shifts" %}</a>
45+
<a class="btn btn-secondary mb-2 {% active_param 'filter' 'own' %}"
46+
href="?filter=own">{% trans "My Shifts" %}</a>
2647
{% if feature.event %}
2748
<div class="dropdown">
2849
<button class="btn btn-secondary dropdown-toggle{% if not public_events %} disabled {% endif %}"

src/shiftings/cal/templates/cal/calendar_templates/shift_type_shifts.html

Whitespace-only changes.

src/shiftings/cal/templates/cal/day_calendar.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,20 @@
88
<div class="base-card bg-dark">
99
<div class="bg-dark center-items justify-content-between p-2 sticky-top">
1010
<div>
11-
<a class="btn btn-success me-1" href="{% url "overview_day" prevday.isoformat %}">
11+
<a class="btn btn-success me-1" href="{{ prev_url }}">
1212
<div class="center-items flex-wrap">
1313
<i class="fa-solid fa-angles-left"></i>
1414
<span class="d-none d-md-block ms-1">{% trans 'Previous Day' context calendar %}</span>
1515
</div>
1616
</a></div>
1717
<div class="center-items">
1818
<h3 class="m-0 me-4">{{ theday|date:'l j. F Y' }}</h3>
19-
<a class="btn btn-success" href="{% url "overview_today" %}" title="{% trans "Jump to Today" %}">
19+
<a class="btn btn-success" href="{{today_url}}" title="{% trans "Jump to Today" %}">
2020
<i class="fa-solid fa-calendar-day"></i>
2121
</a>
2222
</div>
2323
<div>
24-
<a class="btn btn-success ms-1" href="{% url "overview_day" nextday.isoformat %}">
24+
<a class="btn btn-success ms-1" href="{{ next_url }}">
2525
<div class="center-items flex-wrap">
2626
<span class="d-none d-md-block me-1">{% trans 'Next Day' context calendar %}</span>
2727
<i class="fa-solid fa-angles-right"></i>
@@ -30,7 +30,7 @@ <h3 class="m-0 me-4">{{ theday|date:'l j. F Y' }}</h3>
3030
</div>
3131
</div>
3232
<div class="p-2">
33-
{% include "cal/template/day_calendar_xs.html" %}
33+
{% include day_calendar_template %}
3434
</div>
3535
</div>
3636
{% endblock %}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
{% load shifts %}
2+
<div>
3+
{# TODO get data from button #}
4+
{% for vals in shifts.time_containers.values %}
5+
{% for val in vals.values %}
6+
{% for shift in val %}
7+
{% url 'add_participant_self' shift.pk as add_self_url %}
8+
{% simpleformmodal 'addSelfForm'|concat:shift.pk _('Add with Name to Shift') add_self_url request.path %}
9+
{% csrf_token %}
10+
{% for field in add_self_form %}
11+
{% bootstrap_field field %}
12+
{% endfor %}
13+
{% endsimpleformmodal %}
14+
{% endfor %}
15+
{% endfor %}
16+
{% endfor %}
17+
</div>
18+
<div class="table-responsive" style="transform: rotateX(180deg);">
19+
<table class="table table-hover table-striped-columns text-center wy-table-bordered-rows"
20+
style="transform: rotateX(180deg);">
21+
<thead>
22+
<tr class="border-bottom border-3 sticky-top">
23+
<td class="display-6 border-end border-3d-flex align-items-center">{% trans "Time" %}</td>
24+
{% for shift_type in shifts.types %}
25+
<td class="display-6 shift-type border-end border-3" colspan="2"
26+
{% if shift_type.color %}
27+
style="--shift-bg-color: {{ shift_type.color }}; --shift-color: {{ shift_type.text_color }}"
28+
{% endif %}>
29+
{{ shift_type.name }}
30+
</td>
31+
{% empty %}
32+
<td class="display-6 text-warning" colspan="6">{% trans "No Shifts on this day" %}</td>
33+
{% endfor %}
34+
</tr>
35+
</thead>
36+
<tbody>
37+
{% for shift_hour, time_container in shifts.time_containers.items %}
38+
<tr>
39+
<td class="border-end border-3">{{ shift_hour|stringformat:'02d:00' }}</td>
40+
{% for shift_type in shifts.types %}
41+
<td class="border-end overflow-auto" colspan="2">
42+
{% for shift in time_container|get_by_key:shift_type.name %}
43+
{% small_shift_display shift %}
44+
{% endfor %}
45+
</td>
46+
{% endfor %}
47+
</tr>
48+
{% endfor %}
49+
</tbody>
50+
</table>
51+
</div>

src/shiftings/cal/templates/cal/template/day_calendar_xs.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
</td>
3434
<td class="d-none d-md-table-cell text-center">
3535
{% if shift.start.date != theday %}
36-
<a class="link" href="{% url "overview_day" shift.start.date.isoformat %}">
36+
<a class="link" href="{% url "overview_day" theday=shift.start.date.isoformat %}">
3737
{{ shift.start }}
3838
</a>
3939
{% else %}

src/shiftings/cal/urls/__init__.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
from django.urls import path
22

3-
from shiftings.cal.views.day_calendar import DayView
3+
from shiftings.cal.views.day_calendar import DetailDayView, ShiftTypesDayView
44
from shiftings.cal.views.month_calendar import MonthCalenderView
55

66
urlpatterns = [
7-
path('overview/day/', DayView.as_view(), name='overview_today'),
8-
path('overview/day/<theday>/', DayView.as_view(), name='overview_day'),
7+
path('overview/day/detail/', DetailDayView.as_view(), name='overview_today'),
8+
path('overview/day/detail/<theday>/', DetailDayView.as_view(), name='overview_day'),
9+
path('overview/day/shift_types/', ShiftTypesDayView.as_view(), name='overview_today_shift_types'),
10+
path('overview/day/shift_types/<theday>/', ShiftTypesDayView.as_view(), name='overview_day_shift_types'),
911
# path('overview/week/', DayView.as_view(), name='overview_thisweek'),
1012
# path('overview/week/<theweek>/', DayView.as_view(), name='overview_week'),
1113
path('overview/month/', MonthCalenderView.as_view(), name='overview_thismonth'),
Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,76 @@
1+
from abc import ABC, abstractmethod
12
from datetime import date, timedelta
23
from typing import Any
34

45
from django.db.models import Q
6+
from django.urls import reverse
57
from django.utils.translation import gettext_lazy as _
68

79
from shiftings.cal.views.calendar_base import CalendarBaseView
810
from shiftings.shifts.forms.participant import AddSelfParticipantForm
9-
from shiftings.shifts.models import Shift
11+
from shiftings.shifts.models import Shift, ShiftType
1012

1113

12-
class DayView(CalendarBaseView):
14+
class DayView(CalendarBaseView, ABC):
1315
template_name = 'cal/day_calendar.html'
1416
save_path_in_session = True
17+
url_name_suffix = ''
1518

1619
def get_title(self) -> str:
1720
if 'theday' in self.kwargs:
1821
return _('Day {date}').format(date=self.kwargs.get('theday'))
1922
return _('Day Overview')
2023

24+
@abstractmethod
25+
def get_shifts(self, theday: date) -> Any:
26+
raise NotImplementedError('get_shifts needs to be implemented')
27+
2128
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
2229
context = super().get_context_data(**kwargs)
2330
theday = date.fromisoformat(self.kwargs.get('theday')) if 'theday' in self.kwargs else date.today()
24-
shift_filter = (Q(start__date=theday) | Q(end__date=theday, end__gt=theday) |
25-
Q(start__lt=theday, end__gt=theday))
26-
shift_filter &= self.get_filters()
27-
shifts = Shift.objects.filter(shift_filter).order_by('start', 'end', 'shift_type')
2831
context.update({
2932
'theday': theday,
30-
'nextday': theday + timedelta(days=1),
31-
'prevday': theday - timedelta(days=1),
33+
'next_url': reverse('overview_day' + self.url_name_suffix, args=[theday + timedelta(days=1)]),
34+
'prev_url': reverse('overview_day' + self.url_name_suffix, args=[theday - timedelta(days=1)]),
35+
'today_url': reverse('overview_today' + self.url_name_suffix),
3236
'day_hours': list(range(24)),
33-
'shifts': [shift for shift in shifts if shift.can_see(self.request.user)],
37+
'shifts': self.get_shifts(theday),
3438
'add_self_form': AddSelfParticipantForm(self.object, initial={'user': self.request.user}),
3539
})
3640
return context
41+
42+
43+
class DetailDayView(DayView):
44+
extra_context = {
45+
'day_calendar_template': 'cal/template/day_calendar_xs.html'
46+
}
47+
48+
def get_shifts(self, theday: date) -> Any:
49+
shift_filter = (Q(start__date=theday) | Q(end__date=theday, end__gt=theday) |
50+
Q(start__lt=theday, end__gt=theday))
51+
shift_filter &= self.get_filters()
52+
shifts = Shift.objects.filter(shift_filter).order_by('start', 'end', 'shift_type')
53+
return [shift for shift in shifts if shift.can_see(self.request.user)]
54+
55+
56+
class ShiftTypesDayView(DayView):
57+
extra_context = {
58+
'day_calendar_template': 'cal/template/day_calendar_shift_types.html'
59+
}
60+
url_name_suffix = '_shift_types'
61+
62+
def get_shifts(self, theday: date):
63+
shift_filter = Q(start__date=theday) & self.get_filters()
64+
shifts = Shift.objects.filter(shift_filter).order_by('start', 'shift_type')
65+
shifts = [shift for shift in shifts if shift.can_see(self.request.user)]
66+
types = ShiftType.objects.filter(shift__in=shifts).distinct()
67+
shift_idx_type = {
68+
'types': types,
69+
'time_containers': {}
70+
}
71+
for shift in shifts:
72+
if shift.can_see(self.request.user):
73+
shift_idx_type['time_containers'].setdefault(shift.start.hour, {}).setdefault(shift.shift_type.name, []).append(
74+
shift
75+
)
76+
return shift_idx_type

src/shiftings/events/models/event.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,5 +86,9 @@ def needed_slots(self) -> int:
8686
def can_see(self, user: User) -> bool:
8787
return self.public or user.events.filter(pk=self.pk).exists()
8888

89+
def can_participate(self, user: User) -> bool:
90+
return self.public or any(user.has_perm('organizations.participate_in_shift', org)
91+
for org in self.allowed_organizations.all())
92+
8993
def get_absolute_url(self) -> str:
9094
return reverse('event', args=[self.pk])

0 commit comments

Comments
 (0)