feat: add admin views, a booking horizon and confirmation modals
Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
1 parent
8caf618950
commit
1a3640b15c
27 files changed
+1269
-62
No files matched your search
@@ -37,6 +37,11 @@
|
||||
{% set classes = classes ~ ' border-primary bg-primary text-white font-bold' %}
|
||||
{% elseif not day.in_month %}
|
||||
{% set classes = classes ~ ' border-transparent text-gray-300' %}
|
||||
{% elseif day.is_past %}
|
||||
{# Dashed rather than the weekend's grey fill: the two states must not be
|
||||
confusable, and the day stays clickable -- looking back at a past plan is
|
||||
legitimate, only booking it is not. #}
|
||||
{% set classes = classes ~ ' border-dashed border-gray-200 text-gray-400' %}
|
||||
{% elseif day.is_weekend %}
|
||||
{% set classes = classes ~ ' border-transparent bg-gray-50 text-gray-400' %}
|
||||
{% else %}
|
||||
@@ -49,15 +54,16 @@
|
||||
<button type="button"
|
||||
class="{{ classes }}"
|
||||
aria-pressed="{{ day.is_selected ? 'true' : 'false' }}"
|
||||
{%- if day.is_past %} aria-disabled="true" title="Vergangener Tag – keine Buchung mehr möglich"{% endif %}
|
||||
hx-get="{{ path('app_board_day', { type: type.value, date: day.date|date('Y-m-d') }|merge(time_params)) }}"
|
||||
hx-swap="none">
|
||||
<span>{{ day.date|date('j') }}</span>
|
||||
{# Team view: the occupancy count is the whole point of showing the month. #}
|
||||
<span class="text-[10px] {{ day.is_selected ? 'text-white/80' : 'text-gray-400' }}">
|
||||
<span class="text-[10px] {{ day.is_selected ? 'text-white/80' : (day.is_past ? 'text-gray-300' : 'text-gray-400') }}">
|
||||
{{ day.count > 0 ? day.count : '' }}
|
||||
</span>
|
||||
{% if day.mine is not empty %}
|
||||
<span class="absolute top-1 right-1 w-1.5 h-1.5 rounded-full {{ day.is_selected ? 'bg-white' : 'bg-primary' }}"></span>
|
||||
<span class="absolute top-1 right-1 w-1.5 h-1.5 rounded-full {{ day.is_selected ? 'bg-white' : (day.is_past ? 'bg-gray-300' : 'bg-primary') }}"></span>
|
||||
{% endif %}
|
||||
</button>
|
||||
{% endfor %}
|
||||
@@ -68,6 +74,7 @@
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-primary"></span> eigene Buchung
|
||||
</div>
|
||||
<div>gestrichelt = vergangen, nicht mehr buchbar</div>
|
||||
<div>Zahl = belegte Plätze{{ selected_time.allDay ? ' an dem Tag' : ' zwischen ' ~ selected_time.start ~ ' und ' ~ selected_time.end }}</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -32,3 +32,14 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{#
|
||||
Removes whatever modal is open, out of band. Only BookingController::boardResponse() renders
|
||||
it, and only for a confirmation that was opened over another modal -- the confirmation swaps
|
||||
itself away by targeting its own element, which leaves the one underneath behind.
|
||||
|
||||
Never rendered as part of the page: nothing in the layout calls this block.
|
||||
#}
|
||||
{% block modal_dismiss %}
|
||||
<div id="htmx-modal" hx-swap-oob="delete"></div>
|
||||
{% endblock %}
|
||||
@@ -53,6 +53,14 @@
|
||||
{% endif %}
|
||||
|
||||
{% set interactive = not is_past and spot.actionable %}
|
||||
{# An administrator may free somebody else's desk -- a colleague calls in sick and the spot
|
||||
should go back into the pool. Colours are untouched: MINE still means mine, and only
|
||||
clickability widens. Past days stay inert for admins too; there is no past-write path.
|
||||
|
||||
Only a shape that is not already actionable: a partly booked one still opens the create
|
||||
modal, because taking the free window is the more useful act there and an admin can reach
|
||||
that booking from /admin/bookings anyway. #}
|
||||
{% set manageable = not is_past and not interactive and is_granted('ROLE_ADMIN') and spot.bookings is not empty %}
|
||||
|
||||
{# 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. #}
|
||||
@@ -77,8 +85,8 @@
|
||||
{% 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 %}
|
||||
<g class="{{ interactive or manageable ? 'cursor-pointer' : 'cursor-default' }}"
|
||||
{%- if interactive or manageable %} 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)) }}"
|
||||
@@ -90,6 +98,12 @@
|
||||
hx-get="{{ path('app_booking_new', { id: spot.resource.id, date: selected_date|date('Y-m-d') }|merge(time_params)) }}"
|
||||
hx-target="body"
|
||||
hx-swap="beforeend"
|
||||
{%- elseif manageable %}
|
||||
{# Not app_booking_edit: a shape can hold several bookings on one day, and an admin
|
||||
releases rather than reschedules. The modal lists them and frees one. #}
|
||||
hx-get="{{ path('app_admin_spot', { id: spot.resource.id, date: selected_date|date('Y-m-d') }|merge(time_params)) }}"
|
||||
hx-target="body"
|
||||
hx-swap="beforeend"
|
||||
{%- endif %}>
|
||||
<rect x="{{ geometry.x }}" y="{{ geometry.y }}" width="{{ width }}" height="{{ height }}" rx="6"
|
||||
class="{{ fill }} {{ stroke }}" stroke-width="2"></rect>
|
||||
|
||||
Reference in new issue
Block a user