feat: paginate the admin booking list
This commit is contained in:
1 parent
ba64033948
commit
2f5d1d14e4
16 files changed
+504
-75
No files matched your search
@@ -0,0 +1,68 @@
|
||||
{#
|
||||
The Knp paginator's sliding window, styled like the sibling project's admin tables: the bar
|
||||
butts against the bottom border of the table it belongs to, hence border-t-0.
|
||||
|
||||
Every control is a plain link -- the house rule hands navigation to the browser, so paging is a
|
||||
normal request and the page ends up in the URL, next to the filter and sort parameters that
|
||||
query|merge carries along.
|
||||
#}
|
||||
{% if pageCount > 0 %}
|
||||
<div class="bg-gray-100 border border-gray-300 border-t-0 px-4 py-3 flex items-center justify-between sm:px-4">
|
||||
<div class="flex-1 flex items-center justify-between">
|
||||
<div class="text-sm leading-5 text-gray-700">
|
||||
Einträge <span class="font-medium">{{ firstItemNumber }}</span>
|
||||
bis <span class="font-medium">{{ lastItemNumber }}</span>
|
||||
von {{ totalCount }}
|
||||
</div>
|
||||
<div>
|
||||
<nav class="relative z-0 inline-flex">
|
||||
{% if pageCount > 1 %}
|
||||
{% if first is defined and current != first %}
|
||||
<a href="{{ path(route, query|merge({(pageParameterName): first})) }}" class="relative inline-flex items-center px-2 py-2 border border-gray-300 bg-white text-sm leading-5 font-medium hover:bg-secondary hover:text-white" aria-label="Erste Seite">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m18.75 4.5-7.5 7.5 7.5 7.5m-6-15L5.25 12l7.5 7.5" />
|
||||
</svg>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if previous is defined %}
|
||||
<a href="{{ path(route, query|merge({(pageParameterName): previous})) }}" class="-ml-px relative inline-flex items-center px-2 py-2 border border-gray-300 bg-white text-sm leading-5 font-medium hover:bg-secondary hover:text-white" aria-label="Vorherige Seite">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5 8.25 12l7.5-7.5" />
|
||||
</svg>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if current > 3 %}
|
||||
<span class="-ml-px relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm leading-5 font-medium text-gray-700">
|
||||
...
|
||||
</span>
|
||||
{% endif %}
|
||||
{% for page in pagesInRange %}
|
||||
<a href="{{ path(route, query|merge({(pageParameterName): page})) }}" class="-ml-px relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm leading-5 font-medium focus:z-10 hover:bg-secondary {% if page == current %}bg-primary text-white hover:text-white{% else %}bg-white text-gray-700 hover:text-white{% endif %}"{% if page == current %} aria-current="page"{% endif %}>
|
||||
{{ page }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
{% if current < pageCount - 3 %}
|
||||
<span class="-ml-px relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm leading-5 font-medium text-gray-700">
|
||||
...
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if next is defined %}
|
||||
<a href="{{ path(route, query|merge({(pageParameterName): next})) }}" class="-ml-px relative inline-flex items-center px-2 py-2 border border-gray-300 bg-white text-sm leading-5 font-medium hover:bg-secondary hover:text-white" aria-label="Nächste Seite">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" />
|
||||
</svg>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if last is defined and current != last %}
|
||||
<a href="{{ path(route, query|merge({(pageParameterName): last})) }}" class="-ml-px relative inline-flex items-center px-2 py-2 border border-gray-300 bg-white text-sm leading-5 font-medium hover:bg-secondary hover:text-white" aria-label="Letzte Seite">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m5.25 4.5 7.5 7.5-7.5 7.5m6-15 7.5 7.5-7.5 7.5" />
|
||||
</svg>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -0,0 +1,15 @@
|
||||
<a href="{{ options.href }}" class="flex items-center space-x-2">
|
||||
<span>{{ title|raw }}</span>
|
||||
{%- if sorted -%}
|
||||
{%- if direction == 'desc' -%}
|
||||
<svg class="w-4 h-4 shrink-0" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path d="M3 4h13M3 8h9m-9 4h6m4 0l4-4m0 0l4 4m-4-4v12"></path>
|
||||
</svg>
|
||||
{%- else -%}
|
||||
<svg class="w-4 h-4 shrink-0" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path d="M3 4h13M3 8h9m-9 4h9m5-4v12m0 0l-4-4m4 4l4-4"></path>
|
||||
</svg>
|
||||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
</a>
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
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
|
||||
page does not have.
|
||||
|
||||
The appointment table is paginated; the series table is not, because a company has a handful of
|
||||
standing bookings and cutting them across pages would only hide them.
|
||||
#}
|
||||
{% extends 'layout.html.twig' %}
|
||||
|
||||
@@ -25,80 +28,109 @@
|
||||
{{ form_row(form.user) }}
|
||||
</div>
|
||||
<div class="flex justify-end gap-2 pt-2">
|
||||
<a class="button button--neutral" href="{{ path('app_admin_booking') }}">Zurücksetzen</a>
|
||||
<button type="submit" class="button button--primary">Filtern</button>
|
||||
<a class="button button--small button--neutral" href="{{ path('app_admin_booking') }}">Zurücksetzen</a>
|
||||
<button type="submit" class="button button--small button--primary">Filtern</button>
|
||||
</div>
|
||||
{{ form_end(form) }}
|
||||
|
||||
{% if series is not empty %}
|
||||
<h2 class="pt-8">Wiederkehrende Buchungen</h2>
|
||||
<ul class="divide-y divide-gray-200 border-y border-gray-200">
|
||||
{% for entry in series %}
|
||||
<li class="flex items-center justify-between gap-4 py-3">
|
||||
<div>
|
||||
<div class="font-semibold">
|
||||
{{ entry.user.name }}
|
||||
<span class="font-normal text-gray-500">· {{ entry.resource.label }}</span>
|
||||
{%- if entry.resource.group %} <span class="font-normal text-gray-500">· {{ entry.resource.group.label }}</span>{% endif %}
|
||||
</div>
|
||||
<div class="text-sm text-gray-500">
|
||||
{{ entry.describe }} · bis {{ entry.endDate|format_datetime(pattern: 'd. MMMM y') }}
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="button button--small button--neutral"
|
||||
hx-get="{{ path('app_series_end', { id: entry.id, return: 'admin' }) }}"
|
||||
hx-target="body"
|
||||
hx-swap="beforeend">
|
||||
Wiederholung beenden
|
||||
</button>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<h2 class="pt-8 pb-4">Wiederkehrende Buchungen</h2>
|
||||
<div class="data-table-wrapper">
|
||||
<div class="data-table-wrapper__inner text-sm">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Mitarbeiter:in</th>
|
||||
<th>Ressource</th>
|
||||
<th>Gruppe</th>
|
||||
<th>Wiederholung</th>
|
||||
<th>Bis</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for entry in series %}
|
||||
<tr>
|
||||
<td class="font-semibold">{{ entry.user.name }}</td>
|
||||
<td>{{ entry.resource.label }}</td>
|
||||
<td>{{ entry.resource.group ? entry.resource.group.label : '–' }}</td>
|
||||
<td>{{ entry.describe }}</td>
|
||||
<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-target="body"
|
||||
hx-swap="beforeend">
|
||||
Wiederholung beenden
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<h2 class="pt-8">Termine</h2>
|
||||
<p class="subline">
|
||||
<p class="subline pb-4">
|
||||
{{ filter.fromDate|format_datetime(pattern: 'd. MMMM y') }} bis
|
||||
{{ filter.toDate|format_datetime(pattern: 'd. MMMM y') }}
|
||||
</p>
|
||||
|
||||
{% if bookings is empty %}
|
||||
<p class="text-sm text-gray-500">Keine Buchungen in diesem Zeitraum.</p>
|
||||
{% else %}
|
||||
<ul class="divide-y divide-gray-200 border-y border-gray-200">
|
||||
{% for booking in bookings %}
|
||||
<li class="flex items-center justify-between gap-4 py-3">
|
||||
<div>
|
||||
<div class="font-semibold">
|
||||
{{ booking.user.name }}
|
||||
<span class="font-normal text-gray-500">
|
||||
· {{ booking.date|format_datetime(pattern: 'EEE, d. MMM y') }}
|
||||
· {{ booking.timeRange }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-sm text-gray-500">
|
||||
{{ booking.resource.type.label }} · {{ booking.resource.label }}
|
||||
{%- if booking.resource.group %} · {{ booking.resource.group.label }}{% endif %}
|
||||
{% if booking.partOfSeries %}
|
||||
<span class="badge bg-gray-100 text-gray-600 ml-1">wiederkehrend</span>
|
||||
{% endif %}
|
||||
{% if booking.overridden %}
|
||||
<span class="badge bg-amber-100 text-amber-800 ml-1">abweichend</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<a class="text-sm underline shrink-0"
|
||||
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--neutral"
|
||||
hx-get="{{ path('app_booking_release', { id: booking.id, return: 'admin' }) }}"
|
||||
hx-target="body"
|
||||
hx-swap="beforeend">
|
||||
Freigeben
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
<div class="data-table-wrapper">
|
||||
<div class="data-table-wrapper__inner text-sm">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ knp_pagination_sortable(pagination, 'Mitarbeiter:in', 'u.name') }}</th>
|
||||
<th>{{ knp_pagination_sortable(pagination, 'Datum', 'b.date') }}</th>
|
||||
<th>{{ knp_pagination_sortable(pagination, 'Zeit', 'b.startMinute') }}</th>
|
||||
<th>{{ knp_pagination_sortable(pagination, 'Typ', 'r.type') }}</th>
|
||||
<th>{{ knp_pagination_sortable(pagination, 'Ressource', 'r.label') }}</th>
|
||||
<th>Gruppe</th>
|
||||
<th>Status</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for booking in pagination %}
|
||||
<tr>
|
||||
<td class="font-semibold">{{ booking.user.name }}</td>
|
||||
<td class="whitespace-nowrap">{{ booking.date|format_datetime(pattern: 'EEE, d. MMM y') }}</td>
|
||||
<td class="whitespace-nowrap">{{ booking.timeRange }}</td>
|
||||
<td>{{ booking.resource.type.label }}</td>
|
||||
<td>{{ booking.resource.label }}</td>
|
||||
<td>{{ booking.resource.group ? booking.resource.group.label : '–' }}</td>
|
||||
<td>
|
||||
{% if booking.partOfSeries %}
|
||||
<span class="badge bg-gray-100 text-gray-600">wiederkehrend</span>
|
||||
{% endif %}
|
||||
{% if booking.overridden %}
|
||||
<span class="badge bg-amber-100 text-amber-800">abweichend</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<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-target="body"
|
||||
hx-swap="beforeend">
|
||||
Freigeben
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="8" class="text-gray-500">Keine Buchungen in diesem Zeitraum.</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{{ knp_pagination_render(pagination) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in new issue
Block a user