feat: list bookings inside the floor plan shapes
This commit is contained in:
1 parent
78839838f0
commit
a17c61400a
4 files changed
+246
-41
No files matched your search
@@ -7,11 +7,18 @@
|
||||
day on an all-day board, and carried no hx-get either -- so a second person could not book the
|
||||
afternoon at all unless they happened to narrow the span by hand first.
|
||||
|
||||
Who holds what is listed INSIDE the shape rather than in an SVG <title> flyout: a tooltip only
|
||||
appears on hover, so it is useless on touch and invisible when scanning the plan, which is the
|
||||
one thing a floor plan is for. The box therefore has a fixed layout -- label on top, one line
|
||||
per booking below -- and the per-type partials size their boxes to fit MAX_LINES of them.
|
||||
|
||||
Clicking always opens a modal: the create modal when anything is still free, the edit modal
|
||||
when it is yours (which is where changing the times and releasing live). Nothing else is
|
||||
clickable, so the SVG carries no confirm dialogs or inline CSRF tokens.
|
||||
#}
|
||||
|
||||
{% macro spot(spot, geometry, width, height, selected_date, is_past, time_params) %}
|
||||
{% set MAX_LINES = 3 %}
|
||||
{% set state = spot.state.name %}
|
||||
|
||||
{% if state == 'MINE' %}
|
||||
@@ -26,21 +33,32 @@
|
||||
|
||||
{% set interactive = not is_past and spot.actionable %}
|
||||
|
||||
{# The second line: what you would be told at a glance. For a partly-free spot that is the
|
||||
window still available, because that is the reason to click it. #}
|
||||
{% if state == 'MINE' %}
|
||||
{% set caption = spot.own.timeRange.allDay ? 'du' : 'du ' ~ spot.own.timeRange %}
|
||||
{% elseif state == 'PARTLY' %}
|
||||
{% set caption = 'frei ' ~ spot.suggested %}
|
||||
{% elseif spot.bookings|length > 1 %}
|
||||
{% set caption = spot.bookings|length ~ ' Buchungen' %}
|
||||
{% elseif spot.bookings is not empty %}
|
||||
{% set caption = spot.bookings|first.user.name %}
|
||||
{% else %}
|
||||
{% set caption = null %}
|
||||
{# One line per booking, then the window still free if there is one -- that last line is the
|
||||
reason a partly booked shape is worth clicking, so it earns its place. #}
|
||||
{% set lines = [] %}
|
||||
{% for booking in spot.bookings %}
|
||||
{% if booking.user.id == app.user.id %}
|
||||
{% set who = 'du' %}
|
||||
{% else %}
|
||||
{# "Anna Berger" -> "Anna B." : full names do not fit a 140px box at this size. #}
|
||||
{% set parts = booking.user.name|split(' ')|filter(part => part is not empty) %}
|
||||
{% set who = parts|length > 1 ? parts|first ~ ' ' ~ parts|last|slice(0, 1) ~ '.' : booking.user.name %}
|
||||
{% endif %}
|
||||
{% set lines = lines|merge([who ~ (booking.timeRange.allDay ? '' : ' ' ~ booking.timeRange)]) %}
|
||||
{% endfor %}
|
||||
{% if state == 'PARTLY' %}
|
||||
{% set lines = lines|merge(['frei ' ~ spot.suggested]) %}
|
||||
{% endif %}
|
||||
|
||||
{# Overflow rather than spill out of the box. Two lines plus a counter beats three lines and
|
||||
a silent omission. #}
|
||||
{% if lines|length > MAX_LINES %}
|
||||
{% set lines = lines|slice(0, MAX_LINES - 1)|merge(['+' ~ (lines|length - MAX_LINES + 1) ~ ' weitere']) %}
|
||||
{% endif %}
|
||||
|
||||
<g class="{{ interactive ? 'cursor-pointer' : 'cursor-default' }}"
|
||||
{%- if interactive %} role="button"{% endif %}
|
||||
aria-label="{{ spot.resource.label }}{% if lines is empty %} — frei{% else %} — {{ lines|join(', ') }}{% endif %}"
|
||||
{%- if interactive and state == 'MINE' %}
|
||||
hx-get="{{ path('app_booking_edit', { id: spot.own.id }|merge(time_params)) }}"
|
||||
hx-target="body"
|
||||
@@ -52,21 +70,16 @@
|
||||
hx-target="body"
|
||||
hx-swap="beforeend"
|
||||
{%- endif %}>
|
||||
<title>
|
||||
{{- spot.resource.label }}
|
||||
{%- if spot.bookings is empty %} — frei
|
||||
{%- else %}
|
||||
{%- for booking in spot.bookings %} — {{ booking.user.id == app.user.id ? 'du' : booking.user.name }} {{ booking.timeRange }}{% endfor %}
|
||||
{%- for window in spot.free %} — frei {{ window }}{% endfor %}
|
||||
{%- endif -%}
|
||||
</title>
|
||||
<rect x="{{ geometry.x }}" y="{{ geometry.y }}" width="{{ width }}" height="{{ height }}" rx="6"
|
||||
class="{{ fill }} {{ stroke }}" stroke-width="2"></rect>
|
||||
<text x="{{ geometry.x + width / 2 }}" y="{{ geometry.y + (caption ? height / 2 - 4 : height / 2 + 4) }}"
|
||||
|
||||
{# Nothing to list: the colour already says "free", so the label sits centred as before. #}
|
||||
<text x="{{ geometry.x + width / 2 }}" y="{{ geometry.y + (lines is empty ? height / 2 + 4 : 18) }}"
|
||||
text-anchor="middle" class="{{ text }} text-xs font-semibold">{{ spot.resource.label }}</text>
|
||||
{% if caption %}
|
||||
<text x="{{ geometry.x + width / 2 }}" y="{{ geometry.y + height / 2 + 12 }}"
|
||||
text-anchor="middle" class="{{ text }} text-[10px]">{{ caption }}</text>
|
||||
{% endif %}
|
||||
|
||||
{% for line in lines %}
|
||||
<text x="{{ geometry.x + width / 2 }}" y="{{ geometry.y + 34 + loop.index0 * 13 }}"
|
||||
text-anchor="middle" class="{{ text }} text-[10px]">{{ line }}</text>
|
||||
{% endfor %}
|
||||
</g>
|
||||
{% endmacro %}
|
||||
Reference in new issue
Block a user