manager: redo day view to always have one cell/one hour (#21213)

This commit is contained in:
Frédéric Péters 2018-01-17 11:16:47 +01:00
parent ea93550742
commit c932aea280
4 changed files with 65 additions and 48 deletions

View File

@ -88,7 +88,6 @@ a.timeperiod-exception-all {
}
$dayview-column-width: 14vw;
$dayview-row-height: 4.5ex;
.dayview thead th {
width: $dayview-column-width;
@ -98,9 +97,8 @@ $dayview-row-height: 4.5ex;
.dayview tbody th {
box-sizing: border-box;
text-align: left;
padding: 0 2ex;
line-height: $dayview-row-height;
height: $dayview-row-height;
padding: 1ex 2ex;
vertical-align: top;
}
.dayview tbody tr:nth-child(2n+1) th,
@ -111,28 +109,32 @@ $dayview-row-height: 4.5ex;
}
}
.dayview td {
padding: 0.5ex 1ex;
.dayview tbody td {
padding: 0 1ex;
vertical-align: top;
position: relative;
}
/* attr(data-rowspan) is not supported by browsers; emulate it by getting
* the attribute value into a CSS variable. */
@for $i from 2 through 100 {
[data-rowspan="#{$i}"] { --rowspan: #{$i}; }
@for $i from 1 through 60 {
table.hourspan-#{$i} tbody td {
height: calc(#{$i} * 2.5em);
}
}
.dayview div[data-rowspan] {
margin-top: -1ex;
.dayview tbody td div {
z-index: 1;
box-sizing: border-box;
padding: 1ex;
background: #eef;
position: absolute;
width: calc(#{$dayview-column-width} - 2ex);
height: calc(#{$dayview-row-height} * var(--rowspan) - 2ex);
min-height: calc(#{$dayview-row-height} * var(--rowspan) - 2ex);
border: 1px solid #666;
border: 1px solid #aaa;
overflow: hidden;
&:hover {
height: auto;
}
}
span.start-time {
font-size: 80%;
}

View File

@ -25,7 +25,7 @@
{% for period, desk_bookings in view.get_timeperiods %}
{% if forloop.first %}
<table>
<table class="hourspan-{{ hour_span }}">
<thead>
<tr>
<td></td>
@ -39,17 +39,20 @@
<tr>
<th>{{ period|date:"TIME_FORMAT" }}</th>
{% for booking in desk_bookings %}
{% for bookings in desk_bookings %}
<td>
{% if booking %}
<div class="booked" {% if booking.rowspan %}data-rowspan="{{ booking.rowspan }}"{% endif %}>
<a {% if booking.backoffice_url %}href="{{booking.backoffice_url}}"{% endif %}
{% for booking in bookings %}
<div class="booked"
style="height: {{ booking.css_height }}%; top: {{ booking.css_top }}%;"
><span class="start-time">{{booking.event.start_datetime|date:"TIME_FORMAT"}}</span>
<a {% if booking.backoffice_url %}href="{{booking.backoffice_url}}"{% endif %}
>{% if booking.label or booking.user_name %}
{{booking.label}}{% if booking.label and booking.user_name %} - {% endif %} {{booking.user_name}}
{% else %}{% trans "booked" %}{% endif %}</a>
</div>
{% endif %}
</td>{% endfor %}
</div>
{% endfor %}
</td>
{% endfor %}
</tr>
{% if forloop.last %}

View File

@ -175,6 +175,10 @@ class AgendaDayView(DayArchiveView):
def get_context_data(self, **kwargs):
context = super(AgendaDayView, self).get_context_data(**kwargs)
context['agenda'] = self.agenda
try:
context['hour_span'] = max(60 / self.agenda.get_base_meeting_duration(), 1)
except ValueError: # no meeting types defined
context['hour_span'] = 1
context['user_can_manage'] = self.agenda.can_be_managed(self.request.user)
return context
@ -205,31 +209,34 @@ class AgendaDayView(DayArchiveView):
min_timeperiod = min([x.start_time for x in timeperiods])
max_timeperiod = max([x.end_time for x in timeperiods])
interval = datetime.timedelta(minutes=self.agenda.get_base_meeting_duration())
current_date = self.date.replace(hour=min_timeperiod.hour, minute=min_timeperiod.minute)
max_date = self.date.replace(hour=max_timeperiod.hour, minute=max_timeperiod.minute)
interval = datetime.timedelta(minutes=60)
current_date = self.date.replace(hour=min_timeperiod.hour, minute=0)
if max_timeperiod.minute == 0:
max_date = self.date.replace(hour=max_timeperiod.hour, minute=0)
else:
# until the end of the last hour.
max_date = self.date.replace(hour=max_timeperiod.hour + 1, minute=0)
desks = self.agenda.desk_set.all()
while current_date < max_date:
# for each timeslot return the timeslot date and a list of per-desk
# bookings
bookings = []
desks_bookings = [] # list of bookings for each desk
for desk in desks:
booking = None
event = [x for x in self.object_list if x.desk_id == desk.id and x.start_datetime == current_date]
if event:
# if an event exist, check it has a non cancelled booking
event = event[0]
event_bookings = [x for x in event.booking_set.all() if not x.cancellation_datetime]
if event_bookings:
booking = event_bookings[0]
if event.meeting_type.duration > (interval.total_seconds() / 60):
booking.rowspan = int(event.meeting_type.duration / (interval.total_seconds() / 60))
bookings = [] # bookings for this desk
finish_datetime = current_date + interval
for event in [x for x in self.object_list if x.desk_id == desk.id and
x.start_datetime >= current_date and x.start_datetime < finish_datetime]:
# don't consider cancelled bookings
for booking in [x for x in event.booking_set.all() if not x.cancellation_datetime]:
booking.css_top = int(100 * event.start_datetime.minute / 60)
booking.css_height = int(100 * event.meeting_type.duration / 60)
bookings.append(booking)
bookings.append(booking)
desks_bookings.append(bookings)
yield current_date, bookings
yield current_date, desks_bookings
current_date += interval
agenda_day_view = AgendaDayView.as_view()

View File

@ -1192,13 +1192,19 @@ def test_agenda_day_view(app, admin_user, manager_user, api_user):
resp = resp.follow()
assert 'No opening hours this day.' in resp.body # no time pediod
TimePeriod(desk=desk, weekday=today.weekday(),
timeperiod = TimePeriod(desk=desk, weekday=today.weekday(),
start_time=datetime.time(10, 0),
end_time=datetime.time(18, 0)).save()
end_time=datetime.time(18, 0))
timeperiod.save()
resp = app.get('/manage/agendas/%s/' % agenda.id, status=302).follow()
assert not 'No opening hours this day.' in resp.body
assert not 'div class="booked' in resp.body
assert resp.body.count('<tr') == 17
assert resp.body.count('<tr') == 9 # 10->18 (not included)
timeperiod.end_time = datetime.time(18, 30) # end during an hour
timeperiod.save()
resp = app.get('/manage/agendas/%s/' % agenda.id, status=302).follow()
assert resp.body.count('<tr') == 10 # 10->18 (included)
# book some slots
app.reset()
@ -1216,17 +1222,17 @@ def test_agenda_day_view(app, admin_user, manager_user, api_user):
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (
agenda.id, date.year, date.month, date.day))
assert resp.body.count('div class="booked') == 2
assert resp.body.count('data-rowspan') == 0
assert 'hourspan-2' in resp.body # table CSS class
assert 'height: 50%; top: 0%;' in resp.body # booking cells
# create a shorted meeting type, this will double the number of rows and
# the bookings will have to span multiple rows.
# create a shorter meeting type, this will change the table CSS class
# (and visually this will give more room for events)
meetingtype = MeetingType(agenda=agenda, label='Baz', duration=15)
meetingtype.save()
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (
agenda.id, date.year, date.month, date.day))
assert resp.body.count('<tr') == 33
assert resp.body.count('div class="booked') == 2
assert resp.body.count('data-rowspan') == 2
assert 'hourspan-4' in resp.body # table CSS class
# cancel a booking
app.reset()
@ -1240,7 +1246,6 @@ def test_agenda_day_view(app, admin_user, manager_user, api_user):
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (
agenda.id, date.year, date.month, date.day))
assert resp.body.count('div class="booked') == 1
assert resp.body.count('data-rowspan') == 1
# wrong type
agenda2 = Agenda(label=u'Foo bar')