{# Hand-authored floor plan. The geometry below is the drawing; `svgElementId` on each Resource is the key that ties a database row to a shape here. A resource whose id is missing from the map is simply not drawn (and vice versa), which is why app:seed and this map have to agree. Colouring needs no JavaScript at all: the SVG is re-rendered server side for the selected day and span, so free/belegt/deine is decided in Twig. Note "free" is always relative to the span in the header -- a desk booked 09:00-13:00 is free for an afternoon board and belegt for an all-day one. Clicking a shape always opens a modal: the create modal when it is free, the edit modal when it is yours (which is where changing the times and releasing it live). Nothing else is clickable, so the SVG carries no confirm dialogs or inline tokens. #} {% set desk_plan = { 'desk-01': { x: 50, y: 70 }, 'desk-02': { x: 190, y: 70 }, 'desk-03': { x: 330, y: 70 }, 'desk-04': { x: 50, y: 170 }, 'desk-05': { x: 190, y: 170 }, 'desk-06': { x: 330, y: 170 }, 'desk-07': { x: 50, y: 270 }, 'desk-08': { x: 190, y: 270 }, 'desk-09': { x: 330, y: 270 }, 'desk-10': { x: 50, y: 370 }, 'desk-11': { x: 190, y: 370 }, 'desk-12': { x: 330, y: 370 } } %} {% set parking_plan = { 'parking-01': { x: 540, y: 60 }, 'parking-02': { x: 540, y: 125 }, 'parking-03': { x: 540, y: 190 }, 'parking-04': { x: 540, y: 255 }, 'parking-05': { x: 540, y: 320 }, 'parking-06': { x: 540, y: 385 } } %} {% set time_params = selected_time.allDay ? {} : { von: selected_time.start, bis: selected_time.end } %} {% macro spot(resource, geometry, width, height, bookings, selected_date, is_past, time_params) %} {% set own = bookings|filter(b => b.user.id == app.user.id)|first|default(null) %} {% set mine = own is not null %} {% if mine %} {% set fill = 'fill-primary' %}{% set stroke = 'stroke-primary-dark' %}{% set text = 'fill-white' %} {% elseif bookings is not empty %} {% set fill = 'fill-gray-200' %}{% set stroke = 'stroke-gray-400' %}{% set text = 'fill-gray-600' %} {% else %} {% set fill = 'fill-emerald-50' %}{% set stroke = 'stroke-emerald-500' %}{% set text = 'fill-emerald-900' %} {% endif %} {% set interactive = not is_past and (mine or bookings is empty) %} {# What the second line of the shape says. Several bookings can overlap the board's span once part-day bookings exist, so the shape counts them and the tooltip spells them out. #} {% if mine %} {% set caption = own.timeRange.allDay ? 'du' : 'du ' ~ own.timeRange %} {% elseif bookings|length > 1 %} {% set caption = bookings|length ~ ' Buchungen' %} {% elseif bookings is not empty %} {% set caption = bookings|first.user.name %} {% else %} {% set caption = null %} {% endif %} ; closing it is Stimulus, not htmx. #} hx-get="{{ path('app_booking_new', { id: resource.id, date: selected_date|date('Y-m-d') }|merge(time_params)) }}" hx-target="body" hx-swap="beforeend" {%- endif %}> {{- resource.label }} {%- if bookings is empty %} — frei {%- else %} {%- for booking in bookings %} — {{ booking.user.id == app.user.id ? 'du' : booking.user.name }} {{ booking.timeRange }}{% endfor %} {%- endif -%} {{ resource.label }} {% if caption %} {{ caption }} {% endif %} {% endmacro %}

{{ selected_date|format_datetime(pattern: 'EEEE, d. MMMM y') }}

frei belegt deine
{% include 'dashboard/_timerange.html.twig' %} {% if is_past %}

Vergangener Tag — hier lässt sich nichts mehr buchen.

{% endif %} Büro Parkplatz {% for resource in desks %} {% if resource.svgElementId and desk_plan[resource.svgElementId] is defined %} {{ _self.spot(resource, desk_plan[resource.svgElementId], 120, 70, bookings_by_resource[resource.id]|default([]), selected_date, is_past, time_params) }} {% endif %} {% endfor %} {% for resource in parking %} {% if resource.svgElementId and parking_plan[resource.svgElementId] is defined %} {{ _self.spot(resource, parking_plan[resource.svgElementId], 230, 50, bookings_by_resource[resource.id]|default([]), selected_date, is_past, time_params) }} {% endif %} {% endfor %}