feat: add supervisor role

This commit is contained in:
fromme committed 2026-10-05 17:10:02 +02:00
1 parent dcd85bb911
commit 3af326251d
22 files changed
+270 -103

No files matched your search

+9 -8
View File
@@ -29,14 +29,15 @@
{% 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.
{# A supervisor (and so 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 them too; there is no
past-write path.
Only a card 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 %}
modal, because taking the free window is the more useful act there and a supervisor can
reach that booking from /manage/bookings anyway. #}
{% set manageable = not is_past and not interactive and is_granted('ROLE_SUPERVISOR') 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 card is worth clicking, so it earns its place. #}
@@ -78,9 +79,9 @@
hx-target="body"
hx-swap="beforeend"
{%- elseif manageable %}
{# Not app_booking_edit: a card can hold several bookings on one day, and an admin
{# Not app_booking_edit: a card can hold several bookings on one day, and a supervisor
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-get="{{ path('app_manage_spot', { id: spot.resource.id, date: selected_date|date('Y-m-d') }|merge(time_params)) }}"
hx-target="body"
hx-swap="beforeend"
{%- endif %}>
+7 -4
View File
@@ -62,11 +62,12 @@
{% endif %}
{#
The header is deliberately not navigation, so the dashboard is the only place an
admin screen can be reached from.
The header is deliberately not navigation, so the dashboard is the only place the
overview and the admin screens can be reached from. ROLE_SUPERVISOR includes
administrators through role_hierarchy.
#}
{% if is_granted('ROLE_ADMIN') %}
<a href="{{ path('app_admin_booking') }}"
{% if is_granted('ROLE_SUPERVISOR') %}
<a href="{{ path('app_manage_booking') }}"
class="group flex flex-col rounded-md border border-gray-200 p-6 hover:border-primary hover:bg-gray-50">
<div class="flex items-center gap-3 pb-2">
{{ ux_icon('lucide:shield-check', { class: 'h-6 w-6 shrink-0 text-primary' }) }}
@@ -76,7 +77,9 @@
Buchungen aller Mitarbeitenden einsehen und ggf. freigeben.
</p>
</a>
{% endif %}
{% if is_granted('ROLE_ADMIN') %}
<a href="{{ path('app_admin_resource') }}"
class="group flex flex-col rounded-md border border-gray-200 p-6 hover:border-primary hover:bg-gray-50">
<div class="flex items-center gap-3 pb-2">
@@ -1,10 +1,10 @@
{#
What an administrator sees when clicking an occupied shape on the floor plan.
What a supervisor (or an administrator) sees when clicking an occupied shape on the floor plan.
Not the edit modal: a shape can hold several bookings on one day once part-day spans exist,
so "the booking" has no single referent -- and an administrator releases rather than
reschedules. Each row posts to the ordinary app_booking_release route, whose ownership check
admits an admin, so there is still one code path that removes a booking.
so "the booking" has no single referent -- and a supervisor releases rather than reschedules.
Each row posts to the ordinary app_booking_release route, whose ownership check admits
supervisors, so there is still one code path that removes a booking.
#}
{% extends 'htmx_modal.html.twig' %}
@@ -4,7 +4,7 @@
Filtering is a plain GET form -- the house rule hands navigation to the browser. The two
destructive actions open the shared confirmation modal via htmx and then navigate back here,
which is what ?return=admin says; without it a confirmed release swaps board fragments this
which is what ?return=manage says; without it a confirmed release swaps board fragments this
page does not have.
The appointment table is paginated; the series table is not, because a company has a handful of
@@ -28,7 +28,7 @@
{{ form_row(form.user) }}
</div>
<div class="flex justify-end gap-2 pt-2">
<a class="button button--small button--neutral" href="{{ path('app_admin_booking') }}">Zurücksetzen</a>
<a class="button button--small button--neutral" href="{{ path('app_manage_booking') }}">Zurücksetzen</a>
<button type="submit" class="button button--small button--primary">Filtern</button>
</div>
{{ form_end(form) }}
@@ -58,7 +58,7 @@
<td>{{ entry.endDate|format_datetime(pattern: 'd. MMMM y') }}</td>
<td class="text-right">
<button type="button" class="button button--small button--warning"
hx-get="{{ path('app_series_end', { id: entry.id, return: 'admin' }) }}"
hx-get="{{ path('app_series_end', { id: entry.id, return: 'manage' }) }}"
hx-target="body"
hx-swap="beforeend">
Wiederholung beenden
@@ -115,7 +115,7 @@
<a class="button button--small button--neutral"
href="{{ path('app_board', { type: booking.resource.type.value, date: booking.date|date('Y-m-d') }) }}">Im Plan</a>
<button type="button" class="button button--small button--warning"
hx-get="{{ path('app_booking_release', { id: booking.id, return: 'admin' }) }}"
hx-get="{{ path('app_booking_release', { id: booking.id, return: 'manage' }) }}"
hx-target="body"
hx-swap="beforeend">
Freigeben