wip: implement admin crud

This commit is contained in:
Björn Fromme committed 2026-07-16 09:03:42 +02:00
1 parent 306cd6bec4
commit e902a5e298
78 files changed
+2555 -101

No files matched your search

@@ -0,0 +1,9 @@
{% if app.session.flashBag.peek('error')|length > 0 %}
{% include '_partials/_toast.html.twig' with { level: 'warning', messages: app.flashes('error') } %}
{% endif %}
{% if app.session.flashBag.peek('success')|length > 0 %}
{% include '_partials/_toast.html.twig' with { level: 'success', messages: app.flashes('success') } %}
{% endif %}
{% if app.session.flashBag.peek('info')|length > 0 %}
{% include '_partials/_toast.html.twig' with { level: 'info', messages: app.flashes('info') } %}
{% endif %}
+5 -4
View File
@@ -41,9 +41,10 @@
{% import _self as knp_menu %}
<a href="{{ item.uri }}"
class="{{ html_classes('', {
'group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold text-gray-700 hover:bg-gray-50': item.level == 1,
'block rounded-md py-2 pr-2 pl-9 text-sm leading-6 text-gray-700 hover:bg-gray-50': item.level > 1,
'bg-gray-50': matcher.isCurrent(item) or matcher.isAncestor(item)
'group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold': item.level == 1,
'block rounded-md py-2 pr-2 pl-9 text-sm leading-6': item.level > 1,
'text-gray-700 hover:bg-gray-50': not (matcher.isCurrent(item) or matcher.isAncestor(item)),
'bg-gray-100 text-gray-900': matcher.isCurrent(item) or matcher.isAncestor(item),
}) }}"
{{ knp_menu.attributes(item.linkAttributes) }}>
{{ block('label') }}
@@ -73,7 +74,7 @@
{% block label %}
{% if item.extras.icon is defined %}
{{ icon(item.extras.icon, 'h-5 w-5 shrink-0 text-gray-400') }}
{{ icon(item.extras.icon, 'h-5 w-5 shrink-0 ' ~ ((matcher.isCurrent(item) or matcher.isAncestor(item)) ? 'text-gray-600' : 'text-gray-400')) }}
<span class="flex-1">{{ item.label|raw }}</span>
{% else %}
{{ item.label|raw }}
+10
View File
@@ -0,0 +1,10 @@
{% apply spaceless %}
{% for message in messages %}
{% if message is iterable %}
{% set message = message.id | trans(message.parameters | default({}), message.domain | default(null), message.locale | default(null)) | raw %}
{% else %}
{% set message = message | trans | raw %}
{% endif %}
<div {{ stimulus_controller('toast', { 'text': message, 'class': 'toastify--' ~ level}) }}{{ qa_attribute('toast', level)}}></div>
{% endfor %}
{% endapply %}
@@ -0,0 +1,296 @@
{% form_theme form 'forms_admin.html.twig' %}
<h2 class="text-xl font-bold">
Allgemein
</h2>
{{ form_start(form) }}
<div class="grid lg:grid-cols-2 gap-y-4 lg:gap-x-8">
{{ form_row(form.name) }}
{{ form_row(form.code) }}
{{ form_row(form.minPaxSummer) }}
{{ form_row(form.maxAdolescentAge) }}
{{ form_row(form.currency) }}
</div>
<div class="flex justify-between pt-8">
<a href="{{ path('app_admin_accommodation') }}" class="button button--secondary button--small">
zurück
</a>
<button type="submit" class="button button--primary button--small">
speichern
</button>
</div>
{{ form_rest(form) }}
{{ form_end(form) }}
<hr class="my-8">
<h2 class="text-xl font-bold">
Preise
</h2>
<div class="data-table-wrapper mb-4">
<div class="data-table-wrapper__inner text-sm">
<table class="data-table">
<thead>
<tr>
<th></th>
<th>
Datums-Bereich
</th>
<th>
Preis pro Nacht
</th>
<th>
Preise weitere Person
</th>
<th>
Mindestbelegung (Nächte)
</th>
<th>
Unterbelegung
</th>
<th></th>
</tr>
</thead>
<tbody>
{% for price in accommodation.accommodationPrices %}
<tr>
<td></td>
<td>
{{ price.dateFrom | date('d.m.Y') }} - {{ price.dateTo | date('d.m.Y') }}
</td>
<td>
{{ (price.effectivePricePerNight / 100) | format_currency(accommodation.currency) }}
{% if price.pricePerNightOverride is not null %}
<span class="line-through text-xs">{{ (price.pricePerNight / 100) | format_currency(accommodation.currency) }}</span>
{% endif %}
</td>
<td>
{{ (price.effectivePriceAdditionalPerson / 100) | format_currency(accommodation.currency) }}
{% if price.priceAdditionalPersonOverride is not null %}
<span class="line-through text-xs">{{ (price.priceAdditionalPerson / 100) | format_currency(accommodation.currency) }}</span>
{% endif %}
</td>
<td>
{{ price.minNights }}
</td>
<td>
{% if price.acceptUndersubscription %}
{{ icon('check', 'w-4 h-4') }}
{% endif %}
</td>
<td class="text-right">
<twig:dropdown>
<twig:dropdown:link url="{{ path('app_admin_accommodationprice_edit', { 'id': price.id }) }}">
bearbeiten
</twig:dropdown:link>
<twig:dropdown:link url="{{ path('app_admin_accommodationprice_duplicate', { 'id': price.id }) }}">
duplizieren
</twig:dropdown:link>
<twig:dropdown:hxbutton url="{{ path('app_admin_accommodationprice_delete', { 'id': price.id }) }}" warning>
löschen
</twig:dropdown:hxbutton>
</twig:dropdown>
</td>
</tr>
{% else %}
<tr>
<td colspan="2">
Keine Daten
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="flex justify-end">
<a href="{{ path('app_admin_accommodationprice_create', { 'id': accommodation.id }) }}" class="button button--primary button--small">
neu
</a>
</div>
<hr class="my-8">
<h2 class="text-xl font-bold">
Verpflegungsleistungen
</h2>
<div class="data-table-wrapper mb-4">
<div class="data-table-wrapper__inner text-sm">
<table class="data-table">
<thead>
<tr>
<th></th>
<th>
Datums-Bereich
</th>
<th>
Bezeichnung
</th>
<th>
Preis pro Person/Nacht
</th>
<th></th>
</tr>
</thead>
<tbody data-controller="sortable" data-sortable-url-value="{{ path('app_admin_boardservice_sort', { 'id': accommodation.id }) }}">
{% for service in accommodation.boardServices %}
<tr data-sortable-target="item">
<td class="cursor-grab w-6" data-sortable-target="handle">
{{ icon('draggable', 'w-4 h-4') }}
</td>
<td>
{{ service.dateFrom | date('d.m.Y') }} - {{ service.dateTo | date('d.m.Y') }}
</td>
<td>
{{ service.label }}
<input type="hidden" name="sorting[{{ service.id }}]" value="{{ loop.index }}" data-sortable-target="positionField" data-id="{{ service.id }}">
</td>
<td>
{{ (service.price / 100) | format_currency(accommodation.currency) }}
</td>
<td class="text-right">
<twig:dropdown>
<twig:dropdown:link url="{{ path('app_admin_boardservice_edit', { 'id': service.id }) }}">
bearbeiten
</twig:dropdown:link>
<twig:dropdown:link url="{{ path('app_admin_boardservice_duplicate', { 'id': service.id }) }}">
duplizieren
</twig:dropdown:link>
<twig:dropdown:hxbutton url="{{ path('app_admin_boardservice_delete', { 'id': service.id }) }}" warning>
löschen
</twig:dropdown:hxbutton>
</twig:dropdown>
</td>
</tr>
{% else %}
<tr>
<td colspan="5">
Keine Daten
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="flex justify-end">
<a href="{{ path('app_admin_boardservice_create', { 'id': accommodation.id }) }}" class="button button--primary button--small">
neu
</a>
</div>
<hr class="my-8">
<h2 class="text-xl font-bold">
Optionale Zusatzleistungen
</h2>
<div class="data-table-wrapper mb-4">
<div class="data-table-wrapper__inner text-sm">
<table class="data-table">
<thead>
<tr>
<th></th>
<th>
Datums-Bereich
</th>
<th>
Bezeichnung
</th>
<th>
Preistyp
</th>
<th>
Preis
</th>
<th></th>
</tr>
</thead>
<tbody data-controller="sortable" data-sortable-url-value="{{ path('app_admin_additionalservice_sort', { 'id': accommodation.id }) }}">
{% for service in accommodation.additionalServices %}
<tr data-sortable-target="item">
<td class="cursor-grab w-6" data-sortable-target="handle">
{{ icon('draggable', 'w-4 h-4') }}
</td>
<td>
{{ service.dateFrom | date('d.m.Y') }} - {{ service.dateTo | date('d.m.Y') }}
</td>
<td>
{{ service.label }}
<input type="hidden" name="sorting[{{ service.id }}]" value="{{ loop.index }}" data-sortable-target="positionField" data-id="{{ service.id }}">
</td>
<td>
{{ service.type.label | trans }}
</td>
<td>
{{ (service.price / 100) | format_currency(accommodation.currency) }}
</td>
<td class="text-right">
<twig:dropdown>
<twig:dropdown:link url="{{ path('app_admin_additionalservice_edit', { 'id': service.id }) }}">
bearbeiten
</twig:dropdown:link>
<twig:dropdown:link url="{{ path('app_admin_additionalservice_duplicate', { 'id': service.id }) }}">
duplizieren
</twig:dropdown:link>
<twig:dropdown:hxbutton url="{{ path('app_admin_additionalservice_delete', { 'id': service.id }) }}" warning>
löschen
</twig:dropdown:hxbutton>
</twig:dropdown>
</td>
</tr>
{% else %}
<tr>
<td colspan="6">
Keine Daten
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="flex justify-end">
<a href="{{ path('app_admin_additionalservice_create', { 'id': accommodation.id }) }}" class="button button--primary button--small">
neu
</a>
</div>
<hr class="my-8">
{% if cmsData is defined %}
<h2 class="text-xl font-bold">
CMS-Daten
</h2>
<div class="grid lg:grid-cols-2 gap-y-4 lg:gap-x-8">
<div class="space-y-4">
<div>
<h3 class="text-lg font-bold">
Adresse
</h3>
<strong>{{ cmsData.name }}</strong>
<br>
{{ cmsData.address | default('-') | nl2br }}
</div>
<div>
<h3 class="text-lg font-bold">
Beschreibung
</h3>
<div class="[&_p]:pb-4">
{{ cmsData.description | default('-') | raw }}
</div>
</div>
</div>
<div>
<h3 class="text-lg font-bold">
Bilder
</h3>
<div class="grid grid-cols-4 gap-4">
{% for image in cmsData.images.resized.s %}
<img src="{{ image.url }}" alt="{{ image.alt }}" class="block w-full h-auto">
{% endfor %}
</div>
</div>
</div>
{% endif %}
@@ -0,0 +1,6 @@
{% extends 'layout_admin.html.twig' %}
{% block content %}
<twig:page:heading>Gruppenhaus neu</twig:page:heading>
{% include 'admin/accommodation/_form.html.twig' %}
{% endblock %}
@@ -0,0 +1,6 @@
{% extends 'layout_admin.html.twig' %}
{% block content %}
<twig:page:heading>Gruppenhaus {{ accommodation.name }}</twig:page:heading>
{% include 'admin/accommodation/_form.html.twig' %}
{% endblock %}
@@ -0,0 +1,71 @@
{% extends 'layout_admin.html.twig' %}
{% block content %}
<twig:page:heading>Gruppenhäuser</twig:page:heading>
<div class="data-table-wrapper">
<div class="data-table-wrapper__inner text-sm">
<table class="data-table">
<thead>
<tr>
<th>
Name
</th>
<th>
Code
</th>
<th>
erstellt
</th>
<th>
aktualisiert
</th>
<th></th>
</tr>
</thead>
<tbody>
{% for accommodation in accommodations %}
<tr>
<td>
{{ accommodation.name }}
</td>
<td>
{{ accommodation.code }}
</td>
<td>
{{ accommodation.createdAt | date('d.m.Y, H:i') }}
</td>
<td>
{% if accommodation.updatedAt is not null %}
{{ accommodation.updatedAt | date('d.m.Y, H:i') }}
{% else %}
-
{% endif %}
</td>
<td class="text-right">
<twig:dropdown>
<twig:dropdown:link url="{{ path('app_admin_accommodation_edit', { 'id': accommodation.id, 'r': return_url() }) }}">
bearbeiten
</twig:dropdown:link>
<twig:dropdown:hxbutton url="{{ path('app_admin_accommodation_delete', { 'id': accommodation.id, 'r': return_url() }) }}" warning>
löschen
</twig:dropdown:hxbutton>
</twig:dropdown>
</td>
</tr>
{% else %}
<tr>
<td colspan="4">
Keine Daten...
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="flex justify-end">
<a href="{{ path('app_admin_accommodation_create') }}" class="button button--primary button--small">
neu
</a>
</div>
{% endblock %}
@@ -0,0 +1,7 @@
{% extends 'htmx_confirmation_modal.html.twig' %}
{% block content %}
<div>
Möchtest du das Gruppenhaus <em>{{ accommodation.name }}</em> wirklich löschen?
</div>
{% endblock %}
@@ -0,0 +1,24 @@
{% form_theme form 'forms_admin.html.twig' %}
{{ form_start(form) }}
<div class="grid lg:grid-cols-2 gap-y-4 lg:gap-x-8">
{{ form_row(form.dateFrom) }}
{{ form_row(form.dateTo) }}
{{ form_row(form.pricePerNight) }}
{{ form_row(form.pricePerNightOverride) }}
{{ form_row(form.priceAdditionalPerson) }}
{{ form_row(form.priceAdditionalPersonOverride) }}
{{ form_row(form.includedPax) }}
{{ form_row(form.minNights) }}
{{ form_row(form.acceptUndersubscription) }}
</div>
<div class="flex justify-between pt-8">
<a href="{{ path('app_admin_accommodation_edit', { 'id': accommodation.id }) }}" class="button button--secondary button--small">
zurück
</a>
<button type="submit" class="button button--primary button--small">
speichern
</button>
</div>
{{ form_rest(form) }}
{{ form_end(form) }}
@@ -0,0 +1,6 @@
{% extends 'layout_admin.html.twig' %}
{% block content %}
<twig:page:heading>Preis neu für Gruppenhaus {{ accommodation.name }}</twig:page:heading>
{% include 'admin/accommodation_price/_form.html.twig' %}
{% endblock %}
@@ -0,0 +1,6 @@
{% extends 'layout_admin.html.twig' %}
{% block content %}
<twig:page:heading>Preis für Gruppenhaus {{ accommodation.name }}{% if duplicate %} duplizieren{% endif %}</twig:page:heading>
{% include 'admin/accommodation_price/_form.html.twig' %}
{% endblock %}
@@ -0,0 +1,7 @@
{% extends 'htmx_confirmation_modal.html.twig' %}
{% block content %}
<div>
Möchtest du den Gruppenhaus-Preis <em>{{ accommodation.name }}, {{ accommodationPrice.dateFrom | date('d.m.Y') }} - {{ accommodationPrice.dateTo | date('d.m.Y') }}</em> wirklich löschen?
</div>
{% endblock %}
@@ -0,0 +1,21 @@
{% form_theme form 'forms_admin.html.twig' %}
{{ form_start(form) }}
<div class="grid lg:grid-cols-2 gap-y-4 lg:gap-x-8">
{{ form_row(form.label) }}
{{ form_row(form.type) }}
{{ form_row(form.price) }}
{{ form_row(form.selectionGroup) }}
{{ form_row(form.dateFrom) }}
{{ form_row(form.dateTo) }}
</div>
<div class="flex justify-between pt-8">
<a href="{{ path('app_admin_accommodation_edit', { 'id': accommodation.id }) }}" class="button button--secondary button--small">
zurück
</a>
<button type="submit" class="button button--primary button--small">
speichern
</button>
</div>
{{ form_rest(form) }}
{{ form_end(form) }}
@@ -0,0 +1,6 @@
{% extends 'layout_admin.html.twig' %}
{% block content %}
<twig:page:heading>Zusatzleistung neu für Gruppenhaus {{ accommodation.name }}</twig:page:heading>
{% include 'admin/additional_service/_form.html.twig' %}
{% endblock %}
@@ -0,0 +1,6 @@
{% extends 'layout_admin.html.twig' %}
{% block content %}
<twig:page:heading>Zusatzleistung für Gruppenhaus {{ accommodation.name }}{% if duplicate %} duplizieren{% endif %}</twig:page:heading>
{% include 'admin/additional_service/_form.html.twig' %}
{% endblock %}
@@ -0,0 +1,7 @@
{% extends 'htmx_confirmation_modal.html.twig' %}
{% block content %}
<div>
Möchtest du die Zusatzleistung <em>{{ additionalService.label }}, {{ accommodation.name }}</em> wirklich löschen?
</div>
{% endblock %}
@@ -0,0 +1,19 @@
{% form_theme form 'forms_admin.html.twig' %}
{{ form_start(form) }}
<div class="grid lg:grid-cols-2 gap-y-4 lg:gap-x-8">
{{ form_row(form.label) }}
{{ form_row(form.price) }}
{{ form_row(form.dateFrom) }}
{{ form_row(form.dateTo) }}
</div>
<div class="flex justify-between pt-8">
<a href="{{ path('app_admin_accommodation_edit', { 'id': accommodation.id }) }}" class="button button--secondary button--small">
zurück
</a>
<button type="submit" class="button button--primary button--small">
speichern
</button>
</div>
{{ form_rest(form) }}
{{ form_end(form) }}
@@ -0,0 +1,6 @@
{% extends 'layout_admin.html.twig' %}
{% block content %}
<twig:page:heading>Verpflegungsleistung neu für Gruppenhaus {{ accommodation.name }}</twig:page:heading>
{% include 'admin/board_service/_form.html.twig' %}
{% endblock %}
@@ -0,0 +1,6 @@
{% extends 'layout_admin.html.twig' %}
{% block content %}
<twig:page:heading>Verpflegungsleistung für Gruppenhaus {{ accommodation.name }}{% if duplicate %} duplizieren{% endif %}</twig:page:heading>
{% include 'admin/board_service/_form.html.twig' %}
{% endblock %}
@@ -0,0 +1,7 @@
{% extends 'htmx_confirmation_modal.html.twig' %}
{% block content %}
<div>
Möchtest du die Verpflegungsleistung <em>{{ boardService.label }}, {{ accommodation.name }}</em> wirklich löschen?
</div>
{% endblock %}
@@ -1,9 +1,7 @@
{% extends 'layout_admin.html.twig' %}
{% block content %}
<h1 class="text-2xl font-bold pb-8">
Buchungsentwürfe
</h1>
<twig:page:heading>Buchungsentwürfe</twig:page:heading>
<div class="data-table-wrapper">
<div class="data-table-wrapper__inner text-sm">
<table class="data-table">
@@ -44,32 +42,17 @@
{% endif %}
</td>
<td>
{% embed '_partials/_dropdown.html.twig' %}
{% block content %}
<a href="{{ path('app_admin_bookingeditdraft_show', { 'id': draft.id, 'r': return_url() }) }}"
class="text-gray-700 hover:text-primary block px-4 py-2 text-sm"
role="menuitem"
tabindex="-1">
Details
</a>
<a href="{{ path('app_admin_bookingeditdraft_export', { 'id': draft.id }) }}"
class="text-gray-700 hover:text-primary block px-4 py-2 text-sm"
role="menuitem"
tabindex="-1">
XLS-Export
</a>
<button type="button"
class="text-red-500 hover:text-primary block px-4 py-2 text-sm w-full text-left"
role="menuitem"
tabindex="-1"
hx-get="{{ path('app_admin_bookingeditdraft_delete', { 'id': draft.id, 'r': return_url() }) }}"
hx-target="body"
hx-swap="beforeend">
löschen
</button>
{% endblock %}
{% endembed %}
<twig:dropdown>
<twig:dropdown:link url="{{ path('app_admin_bookingeditdraft_show', { 'id': draft.id, 'r': return_url() }) }}">
Details
</twig:dropdown:link>
<twig:dropdown:link url="{{ path('app_admin_bookingeditdraft_export', { 'id': draft.id }) }}">
XLS-Export
</twig:dropdown:link>
<twig:dropdown:hxbutton url="{{ path('app_admin_bookingeditdraft_delete', { 'id': draft.id, 'r': return_url() }) }}" warning>
löschen
</twig:dropdown:hxbutton>
</twig:dropdown>
</td>
</tr>
{% else %}
@@ -1,9 +1,7 @@
{% extends 'layout_admin.html.twig' %}
{% block content %}
<h1 class="text-2xl font-bold pb-8">
Buchungsentwurf
</h1>
<twig:page:heading>Buchungsentwurf</twig:page:heading>
<h2 class="text-xl font-bold pb-4">
Vorgang {{ draft.bookingNumber }}
</h2>
+5 -12
View File
@@ -1,9 +1,7 @@
{% extends 'layout_admin.html.twig' %}
{% block content %}
<h1 class="text-2xl font-bold pb-8">
Logs
</h1>
<twig:page:heading>Logs</twig:page:heading>
<div class="data-table-wrapper">
<div class="data-table-wrapper__inner text-sm">
<table class="data-table">
@@ -53,16 +51,11 @@
</td>
<td>
{% if entry.channel == 'bpn' %}
{% embed '_partials/_dropdown.html.twig' %}
{% block content %}
<a href="{{ path('app_admin_log_xmldumps', { 'id': entry.id }) }}"
class="text-gray-700 hover:text-primary block px-4 py-2 text-sm"
role="menuitem"
tabindex="-1">
<twig:dropdown>
<twig:dropdown:link url="{{ path('app_admin_log_xmldumps', { 'id': entry.id }) }}">
XML-Dumps
</a>
{% endblock %}
{% endembed %}
</twig:dropdown:link>
</twig:dropdown>
{% endif %}
</td>
</tr>
+3 -5
View File
@@ -1,9 +1,7 @@
{% extends 'layout_admin.html.twig' %}
{% block content %}
<h1 class="text-2xl font-bold pb-8">
XML-Dumps für Request ID {{ logEntry.requestId }}
</h1>
<twig:page:heading>XML-Dumps für Request ID {{ logEntry.requestId }}</twig:page:heading>
{% if dumps is empty %}
<div class="alert alert-warning">
<i class="fa fa-exclamation-triangle"></i>
@@ -37,9 +35,9 @@
</td>
<td>
{% if dump.type == 'request' %}
<span class="badge bg-primary">Request</span>
<twig:badge>Request</twig:badge>
{% else %}
<span class="badge bg-success">Response</span>
<twig:badge variant="success">Response</twig:badge>
{% endif %}
</td>
<td>
+1 -3
View File
@@ -1,9 +1,7 @@
{% extends 'layout_admin.html.twig' %}
{% block content %}
<h1 class="text-2xl font-bold pb-8">
Benutzeraccounts
</h1>
<twig:page:heading>Benutzeraccounts</twig:page:heading>
<div class="data-table-wrapper">
<div class="data-table-wrapper__inner text-sm">
<table class="data-table">
+2 -2
View File
@@ -9,10 +9,10 @@
<link rel="icon" href="{{ asset('build/favicon/icon.svg') }}" type="image/svg+xml">
<link rel="apple-touch-icon" href="{{ asset('build/favicon/apple-touch-icon.png') }}">
{% block stylesheets %}
{{ encore_entry_link_tags('app') }}
{{ encore_entry_link_tags('admin') }}
{% endblock %}
{% block javascripts %}
{{ encore_entry_script_tags('app') }}
{{ encore_entry_script_tags('admin') }}
{% endblock %}
</head>
<body class="bg-white text-gray-700 font-sans antialiased {{ booking_theme() }}">
+11
View File
@@ -0,0 +1,11 @@
{% set variant = variant ?? 'primary' %}
{% set cva = html_cva('badge', {
variant: {
primary: 'bg-primary text-white',
success: 'bg-green-500 text-white',
warning: 'bg-red-500 text-white',
}
}, [], { variant: 'primary' }) %}
<span {{ attributes.defaults({class: cva.apply({variant: variant})}).without('variant') }}>
{{ block('content') }}
</span>
@@ -0,0 +1,20 @@
{% set warning = warning ?? false %}
{% set method = method ?? 'get' %}
<button type="button"
class="{{ html_classes('block px-4 py-2 text-sm w-full text-left', {
'text-red-500 hover:text-primary': warning,
'text-gray-700 hover:text-primary': not warning
}) }}"
role="menuitem"
tabindex="-1"
{{ attributes
.defaults({
'hx-target': 'body',
'hx-swap': 'beforeend',
})
.without('url', 'warning', 'method')
}}
hx-{{ method | lower }}="{{ url }}"
>
{{ block('content') }}
</button>
@@ -1,9 +1,16 @@
<div class="relative inline-block text-left group">
<button type="button" class="flex items-center text-gray-400 hover:text-gray-600 focus:outline-none focus:ring-0" aria-expanded="true" aria-haspopup="true">
<div class="relative inline-block text-left group cursor-pointer">
<button type="button"
class="flex items-center text-gray-400 hover:text-gray-600 focus:outline-none focus:ring-0"
aria-expanded="true"
aria-haspopup="true">
<span class="sr-only">Funktionen</span>
{{ icon('dots', 'w-6 h-6') }}
</button>
<div class="hidden group-hover:block absolute right-0 top-0 z-10 w-56 origin-top-right pt-4" role="menu" aria-orientation="vertical" aria-labelledby="menu-button" tabindex="-1">
<div class="hidden group-hover:block absolute right-0 top-0 z-10 w-56 origin-top-right pt-4"
role="menu"
aria-orientation="vertical"
aria-labelledby="menu-button"
tabindex="-1">
<div class="rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
<div class="py-1 menu menu--pulldown" role="none">
{% block content %}{% endblock %}
@@ -0,0 +1,10 @@
{% set warning = warning ?? false %}
<a href="{{ url }}"
class="{{ html_classes('block px-4 py-2 text-sm w-full', {
'text-red-500 hover:text-primary': warning,
'text-gray-700 hover:text-primary': not warning
}) }}"
role="menuitem"
tabindex="-1">
{{ block('content') }}
</a>
@@ -0,0 +1 @@
<h1 class="text-2xl font-bold pb-8">{{ block('content') }}</h1>
+235
View File
@@ -0,0 +1,235 @@
{% use 'form_div_layout.html.twig' %}
{%- block form_row -%}
{%- set widget_attr = {} -%}
{%- if help is not empty -%}
{%- set widget_attr = {attr: {'aria-describedby': id ~"_help"}} -%}
{%- endif -%}
<div{% with {attr: row_attr} %}{{ block('attributes') }}{% endwith %}>
{{- form_label(form) -}}
{{- form_widget(form, widget_attr) -}}
{{- form_errors(form) -}}
{{- form_help(form) -}}
</div>
{%- endblock form_row -%}
{%- block form_label -%}
{% set class = 'block font-bold pb-2' %}
{% if errors|length %}
{% set class = class ~ ' text-red-500' %}
{% endif %}
{% set label_attr = label_attr|merge({'class': (label_attr.class|default('') ~ class)|trim}) %}
{{ parent() }}
{%- endblock form_label -%}
{%- block form_errors -%}
{%- if errors|length > 0 -%}
<ul class="pb-2">
{%- for error in errors -%}
<li class="text-red-500">{{ error.message }}</li>
{%- endfor -%}
</ul>
{%- endif -%}
{%- endblock form_errors -%}
{%- block form_widget_simple -%}
{%- set type = type|default('text') -%}
{%- if type != 'hidden' -%}
{%- set attr = attr|merge({'class': (attr.class|default('') ~ ' block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset text-sm')|trim }) -%}
{%- if errors|length -%}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' ring-red-500 placeholder-red-500 focus:ring-red-500' }) -%}
{% else %}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' placeholder:text-gray-400 focus:ring-primary' }) -%}
{%- endif -%}
{%- if disabled is defined and disabled == true -%}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' cursor-not-allowed' }) -%}
{%- endif -%}
{%- endif -%}
{{ parent() }}
{%- if disabled is defined and disabled == true -%}
<input type="hidden" name="{{ form.vars.full_name }}" value="{{ form.vars.value }}">
{%- endif -%}
{%- endblock form_widget_simple -%}
{%- block textarea_widget -%}
{%- set attr = attr|merge({'class': (attr.class|default('') ~ ' block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset text-sm')|trim }) -%}
{%- if errors|length -%}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' ring-red-500 placeholder-red-500 focus:ring-red-500' }) -%}
{% else %}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' placeholder:text-gray-400 focus:ring-primary' }) -%}
{%- endif -%}
{%- if disabled is defined and disabled == true -%}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' cursor-not-allowed' }) -%}
{%- endif -%}
{{ parent() }}
{%- if disabled is defined and disabled == true -%}
<input type="hidden" name="{{ form.vars.full_name }}" value="{{ form.vars.value }}">
{%- endif -%}
{%- endblock textarea_widget -%}
{%- block checkbox_widget -%}
{%- set attr = attr|merge({'class': (attr.class|default('') ~ ' h-4 w-4 rounded text-primary')|trim }) -%}
{%- if errors|length -%}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' border-red-500 text-red-500 ring-red-500 focus:ring-red-500' }) -%}
{% else %}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' border-gray-300 focus:ring-primary' }) -%}
{%- endif -%}
{%- if attr.disabled is defined and attr.disabled == true -%}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' cursor-not-allowed' }) -%}
{%- endif -%}
<input type="checkbox" {{ block('widget_attributes') }}{% if value is defined %} value="{{ value }}"{% endif %}{% if checked %} checked="checked"{% endif %} />
{%- endblock checkbox_widget -%}
{% block checkbox_row %}
<div class="relative flex items-start">
<div class="flex h-6 items-center">
{{ form_widget(form) }}
</div>
<div class="{{ html_classes('ml-3 leading-6', { 'text-red-500': errors|length }) }}">
<label for="{{ form.vars.id }}">
{{ form.vars.label }}
</label>
{{- form_errors(form) -}}
</div>
</div>
{% endblock %}
{%- block choice_widget_collapsed -%}
{%- set attr = attr|merge({'class': (attr.class|default('') ~ ' block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset text-sm')|trim }) -%}
{%- if errors|length -%}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' ring-red-500 placeholder-red-500 focus:ring-red-500' }) -%}
{% else %}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' placeholder:text-gray-400 focus:ring-primary' }) -%}
{%- endif -%}
{%- if disabled is defined and disabled == true -%}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' cursor-not-allowed' }) -%}
{%- endif -%}
{{ parent() }}
{%- if disabled is defined and disabled == true -%}
<input type="hidden" name="{{ form.vars.full_name }}" value="{{ form.vars.value }}">
{%- endif -%}
{%- endblock choice_widget_collapsed -%}
{%- block choice_widget_expanded -%}
{%- for child in form %}
<div class="relative flex items-start py-2 first:pt-0">
<div class="min-w-0 flex-1 text-sm leading-4">
<label for="{{ child.vars.id }}" class="select-none font-medium text-gray-900">
{{ child.vars.label }}
</label>
</div>
<div class="ml-3 flex h-4 items-center">
{{- form_widget(child) -}}
</div>
</div>
{% endfor -%}
{%- endblock choice_widget_expanded -%}
{%- block birthday_widget -%}
<div class="grid grid-cols-3 gap-x-1">
{{ form_widget(form.children['day']) }}
{{ form_widget(form.children['month']) }}
{{ form_widget(form.children['year']) }}
</div>
{%- endblock -%}
{% block abbreviated_date_widget %}
{%- set class = 'flex items-center space-x-1 ' ~ attr.class|default('') -%}
{%- do form.setRendered -%}
{%- if errors|length -%}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' ring-red-500 placeholder-red-500 focus:ring-red-500' }) -%}
{%- endif -%}
<div class="{{ class }}">
{{ form_widget(form.month, { 'attr': attr })}}
<span>/</span>
{{ form_widget(form.year, { 'attr': attr })}}
</div>
{% endblock %}
{%- block money_widget -%}
{% set currency_class = 'absolute top-1/2 transform -translate-y-1/2 right-0 mr-2' %}
{% if errors|length %}
{% set currency_class = currency_class ~ ' text-red-500' %}
{% else %}
{% set currency_class = currency_class ~ ' text-gray-600' %}
{% endif %}
<div class="relative">
{{ block('form_widget_simple') }}
<span class="{{ currency_class }}">€</span>
</div>
{%- endblock money_widget -%}
{%- block datepicker_widget -%}
{%- set minDate = form.vars.min_date ? form.vars.min_date | date('Y-m-d') : null -%}
{%- set maxDate = form.vars.max_date ? form.vars.max_date | date('Y-m-d') : null -%}
{%- set attr = attr|merge({'class': (attr.class|default('') ~ ' block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset text-sm')|trim }) -%}
{%- if errors|length -%}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' ring-red-500 placeholder:red-500 focus:ring-red-500' }) -%}
{% else %}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' placeholder:text-gray-900 focus:ring-primary' }) -%}
{%- endif -%}
{%- if disabled is defined and disabled == true -%}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' cursor-not-allowed' }) -%}
{%- endif -%}
<div {{ stimulus_controller('datepicker', { 'minDate': minDate, 'maxDate': maxDate, 'disableWeekends': form.vars.disable_weekends }) }}>
<input type="text" name="{{ form.vars.full_name }}" value="{{ value }}" {{ block('widget_attributes') }} {{ stimulus_target('datepicker', 'field') }} />
</div>
{%- if disabled is defined and disabled == true -%}
<input type="hidden" name="{{ form.vars.full_name }}" value="{{ value }}">
{%- endif -%}
{%- endblock datepicker_widget %}
{%- block autocomplete_widget -%}
{%- set attr = attr|merge({'class': (attr.class|default('') ~ ' autocomplete-input block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset text-sm')|trim }) -%}
{%- if errors|length -%}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' ring-red-500 placeholder-red-500 focus:ring-red-500' }) -%}
{% else %}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' placeholder:text-gray-400 focus:ring-primary' }) -%}
{%- endif -%}
{%- if disabled is defined and disabled == true -%}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' cursor-not-allowed' }) -%}
{%- endif -%}
<div class="autocomplete"
{{ stimulus_controller('autocomplete', {
'action': form.vars.action ?? null,
'endpointUrl': form.vars.endpoint_url ?? null,
'choices': form.vars.choices ?? null
}) }}
>
<input type="text"
class="{{ attr.class }}"
value="{{ form.vars.initial_label }}"
{{ stimulus_target('autocomplete', 'input') }}>
<ul class="autocomplete-result-list"></ul>
<input type="hidden"
name="{{ form.vars.full_name }}"
{{ stimulus_target('autocomplete', 'field') }}
value="{{ form.vars.value }}">
<button type="button"
class="autocomplete-icon hidden"
{{ stimulus_action('autocomplete', 'reset') }}
{{ stimulus_target('autocomplete', 'resetButton') }}>
{{ icon('close', 'w-4 h-4 text-gray-700') }}
</button>
<div class="autocomplete-icon"
{{ stimulus_target('autocomplete', 'searchIcon') }}>
{{ icon('search', 'w-4 h-4 text-gray-700') }}
</div>
</div>
{%- endblock autocomplete_widget -%}
{%- block multiselect_widget -%}
<div class="relative" {{ stimulus_controller('multiselect', { 'placeholder': form.vars.empty_label }) }}>
<div {{ stimulus_action('multiselect', 'toggle', 'click')}}
role="button"
class="relative rounded-md border border-gray-300 flex items-start space-x-2 px-3">
<div {{ stimulus_target('multiselect', 'label') }} class="flex-1 min-h-8 py-1.5 text-gray-900 text-sm"></div>
{{ icon('dots', 'w-4 h-4 mt-2') }}
</div>
<div {{ stimulus_target('multiselect', 'dropdown')}} class="absolute z-10 top-full -mt-1.5 left-0 inset-x-0 max-h-48 overflow-y-scroll px-3 py-2 bg-white shadow-lg rounded-md rounded-t-none border border-gray-300 border-t-0 hidden">
{% for item in form.children %}
{{ form_row(item, { 'attr': { 'data-action': 'multiselect#update', 'data-multiselect-target': 'choice', 'data-label': item.vars.label } }) }}
{% endfor %}
</div>
</div>
{%- endblock multiselect_widget -%}
+2 -1
View File
@@ -17,7 +17,7 @@
</div>
{% endif %}
</div>
<div class="hidden lg:block">
<div class="hidden lg:block sticky top-0 h-screen">
{{ knp_menu_render(knp_menu_get('admin_main')) }}
</div>
<div class="lg:col-span-3">
@@ -41,4 +41,5 @@
</div>
</div>
</div>
{% include '_partials/_flashes_admin.html.twig' %}
{% endblock %}