feat: streamlined teamer info modals, internal teamer remarks

This commit is contained in:
Björn Fromme
2025-02-10 18:14:19 +01:00
parent 0b743172e7
commit 9192e8220d
13 changed files with 201 additions and 86 deletions
+33
View File
@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20250210162751 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE teamer ADD remarks_internal LONGTEXT DEFAULT NULL');
$this->addSql('ALTER TABLE user DROP hotel_code');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE teamer DROP remarks_internal');
$this->addSql('ALTER TABLE user ADD hotel_code VARCHAR(32) DEFAULT NULL');
}
}
@@ -1,19 +0,0 @@
<?php
namespace App\Controller\Admin\Application;
use App\Entity\Application;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class InfoController extends AbstractController
{
#[Route('/admin/application/info/{uuid}', name: 'app_admin_application_info')]
public function index(Application $application): Response
{
return $this->render('admin/application/modal_info.html.twig', [
'application' => $application,
]);
}
}
@@ -0,0 +1,67 @@
<?php
namespace App\Controller\Admin\Teamer;
use App\Controller\Traits\ReturnUrlTrait;
use App\Entity\Teamer;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
class RemarksController extends AbstractController
{
use ReturnUrlTrait;
public function __construct(private readonly EntityManagerInterface $entityManager)
{}
#[Route('/admin/teamer/remarks/{uuid}', name: 'app_admin_teamer_remarks_internal')]
#[IsGranted('ROLE_ADMIN')]
public function index(Teamer $teamer, Request $request): Response
{
$returnUrl = $this->getReturnUrl($request, 'app_administrative_teamer_index');
$form = $this
->createFormBuilder([
'remarks' => $teamer->getRemarksInternal(),
], [
'action' => $this->generateUrl('app_admin_teamer_remarks_internal', [
'uuid' => $teamer->getUuid(),
'r' => $returnUrl,
])
])
->add('remarks', TextareaType::class, [
'label' => 'Anmerkungen',
'required' => true,
'attr' => [
'rows' => 10,
],
])
->getForm()
;
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$teamer->setRemarksInternal($form->get('remarks')->getData());
$this->entityManager->flush();
$this->addFlash('success', 'Die Anmerkungen wurden gespeichert.');
return $this->redirectToRoute('app_admin_teamer_remarks_internal', [
'uuid' => $teamer->getUuid(),
'r' => $returnUrl,
]);
}
return $this->render('admin/teamer/remarks_internal.html.twig', [
'teamer' => $teamer,
'form' => $form->createView(),
'returnUrl' => $returnUrl,
]);
}
}
@@ -51,8 +51,18 @@ class CreateController extends AbstractController
$yearFrom = (int) date('Y');
$yearsRange = range($yearFrom + 1, $yearFrom - 10);
$returnUrl = $this->getReturnUrl($request, 'app_admin_teamer_skills', [
'uuid' => $teamer->getUuid(),
]);
$form = $this
->createFormBuilder($attendance)
->createFormBuilder($attendance, [
'action' => $this->generateUrl('app_admin_teamer_skills_training_attendance_create', [
'training_id' => $training->getId(),
'teamer_id' => $teamer->getId(),
'r' => $returnUrl,
])
])
->add('date', AbbreviatedDateType::class, [
'label' => 'Datum',
'years' => $yearsRange,
@@ -79,11 +89,7 @@ class CreateController extends AbstractController
'training_name' => $training->getName(),
]);
$redirectUrl = $this->getReturnUrl($request, 'app_admin_teamer_skills', [
'uuid' => $teamer->getUuid(),
]);
return new HxRedirectResponse($redirectUrl);
return new HxRedirectResponse($returnUrl);
}
return $this->render('admin/teamer/training_attendance/modal_create.html.twig', [
@@ -2,28 +2,35 @@
namespace App\Controller\Administrative\Teamer;
use App\Controller\Traits\ReturnUrlTrait;
use App\Entity\Teamer;
use App\Repository\DispositionRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
class InfoController extends AbstractController
{
use ReturnUrlTrait;
public function __construct(private readonly DispositionRepository $dispositionRepository)
{}
#[Route('/administrative/teamer/info/{uuid}', name: 'app_administrative_teamer_info')]
#[IsGranted(new Expression('is_granted("ROLE_ADMINISTRATIVE") or is_granted("ROLE_HOUSE_MANAGER")'))]
public function index(Teamer $teamer): Response
public function index(Teamer $teamer, Request $request): Response
{
$returnUrl = $this->getReturnUrl($request, 'app_administrative_assignment_index');
$recentDispositions = $this->dispositionRepository->findRecentDispositionsByTeamer($teamer);
return $this->render('administrative/teamer/modal_info.html.twig', [
'teamer' => $teamer,
'recentDispositions' => $recentDispositions,
'returnUrl' => $returnUrl,
]);
}
}
+15
View File
@@ -83,6 +83,9 @@ class Teamer implements TimestampableEntityInterface
#[ORM\Column(type: Types::TEXT, nullable: true)]
private ?string $remarks = null;
#[ORM\Column(type: Types::TEXT, nullable: true)]
private ?string $remarksInternal = null;
#[ORM\ManyToMany(targetEntity: Availability::class, inversedBy: 'teamers')]
#[ORM\OrderBy(['dateFrom' => 'ASC'])]
private Collection $availabilities;
@@ -388,6 +391,18 @@ class Teamer implements TimestampableEntityInterface
return $this;
}
public function getRemarksInternal(): ?string
{
return $this->remarksInternal;
}
public function setRemarksInternal(?string $remarksInternal): static
{
$this->remarksInternal = $remarksInternal;
return $this;
}
/**
* @return Collection<int, Availability>
*/
+7
View File
@@ -285,6 +285,13 @@ class AdminMenuBuilder extends AbstractMenuBuilder
'title' => 'Vergangene Einsätze',
],
]);
$menu->addChild('Anmerkungen (intern)', [
'route' => 'app_admin_teamer_remarks_internal',
'routeParameters' => $this->getDefaultRouteParameters('uuid'),
'linkAttributes' => [
'title' => 'Anmerkungen (intern)',
],
]);
$this->addDivider($menu);
+2 -1
View File
@@ -64,7 +64,8 @@
{% elseif item.extras.divider is defined %}
<hr class="my-4 h-px bg-gray-200">
{% else %}
<div class="hover:bg-gray-50 group flex gap-x-3 rounded-md p-2 text-sm leading-5 font-semibold text-gray-700" role="button">
<div class="hover:bg-gray-50 group flex gap-x-3 rounded-md p-2 text-sm leading-5 font-semibold text-gray-700"
role="button">
{{ block('label') }}
</div>
{% endif %}
@@ -1,50 +0,0 @@
{% extends 'htmx_modal.html.twig' %}
{% block title %}Teamer:inneninfo {{ application.teamer }}{% endblock %}
{% block content %}
<div class="flex items-start">
<div class="flex-1 pr-8">
{% include '_partials/_assignment_requirements_info.html.twig' with {
'assignment': application.assignment,
'teamer': application.teamer
} %}
<ul class="pb-8 divide-y divide-gray-200">
{% for type in ['ski', 'snowboard'] %}
<li class="py-2">
<div class="flex items-center justify-between">
<span class="flex-1">{{ type|license_label }}</span>
{% set license = application.teamer.licenseOfType(type) %}
{% if license %}
<div class="flex items-center space-x-2">
<span>{{ license.date|date('m/Y') }}</span>
{% if is_granted('DOWNLOAD', license.certificate) %}
<a href="{{ path('app_common_download', { 'uuid': license.certificate.uuid, 'inline': true }) }}"
target="_blank"
title="Download Nachweis">
{{ icon('download', 'w-4 h-4') }}
</a>
{% endif %}
</div>
{% else %}
{{ icon('minus', 'w-4 h-4') }}
{% endif %}
</div>
</li>
{% endfor %}
</ul>
</div>
<div>
{% set teamer = application.teamer %}
{% if teamer.photo %}
<img src="{{ asset(teamer.photo.filename | imagine_filter('profile')) }}"
class="w-32 h-auto border-2 border-primary"
alt="{{ teamer.firstName }}">
{% else %}
<div class="border-2 border-primary">
{{ icon('user', 'w-32 h-32 text-gray-200') }}
</div>
{% endif %}
</div>
</div>
{% endblock %}
@@ -0,0 +1,25 @@
{% extends 'admin/layout.html.twig' %}
{% block title %}Interne Anmerkungen zu {{ teamer }}{% endblock %}
{% block content %}
<div class="grid lg:grid-cols-3 gap-y-8 lg:gap-y-0 lg:gap-x-8">
<div>
{{ knp_menu_render(knp_menu_get('admin_teamer')) }}
</div>
<div class="lg:col-span-2">
<h1 class="text-2xl font-bold pb-8">
Interne Anmerkungen zu {{ teamer }}
</h1>
{{ form_start(form) }}
<div class="pb-4">
{{ form_row(form.remarks) }}
</div>
<button type="submit" class="btn">
speichern
</button>
{{ form_rest(form) }}
{{ form_end(form) }}
</div>
</div>
{% endblock %}
+8 -2
View File
@@ -27,7 +27,9 @@
<button type="button"
class="text-red-500"
title="Ausbildung löschen"
hx-get="{{ path('app_admin_teamer_skills_training_attendance_delete', { 'uuid': attendance.uuid, 'r': return_url() }) }}"
hx-get="{{ path('app_admin_teamer_skills_training_attendance_delete', {
'uuid': attendance.uuid, 'r': return_url()
}) }}"
hx-target="body"
hx-swap="beforeend">
{{ icon('delete') }}
@@ -36,7 +38,11 @@
{% else %}
<button type="button"
title="Teilnahme hinzufügen"
hx-get="{{ path('app_admin_teamer_skills_training_attendance_create', { 'training_id': training.id, 'teamer_id': teamer.id, 'r': return_url() }) }}"
hx-get="{{ path('app_admin_teamer_skills_training_attendance_create', {
'training_id': training.id,
'teamer_id': teamer.id,
'r': return_url()
}) }}"
hx-target="body"
hx-swap="beforeend">
{{ icon('calendar', 'w-4 h-4') }}
@@ -41,7 +41,10 @@
<button type="button"
class="flex items-center space-x-1"
title="Teamer:inneninfo {{ application.teamer }}"
hx-get="{{ path('app_admin_application_info', { 'uuid': application.uuid }) }}"
hx-get="{{ path('app_administrative_teamer_info', {
'uuid': application.teamer.uuid,
'r': return_url()
}) }}"
hx-target="body"
hx-swap="beforeend">
<span>{{ application.teamer }}</span>
@@ -68,7 +71,9 @@
<button type="button"
class="text-green-500"
title="einteilen"
hx-get="{{ path('app_admin_application_dispose', { 'uuid': application.uuid }) }}"
hx-get="{{ path('app_admin_application_dispose', {
'uuid': application.uuid
}) }}"
hx-target="body"
hx-swap="beforeend">
{{ icon('check') }}
@@ -78,7 +83,9 @@
<button type="button"
class="text-primary"
title="Bewerbungsstatus bearbeiten"
hx-get="{{ path('app_admin_application_status', { 'uuid': application.uuid }) }}"
hx-get="{{ path('app_admin_application_status', {
'uuid': application.uuid
}) }}"
hx-target="body"
hx-swap="beforeend">
{{ icon('edit') }}
@@ -88,7 +95,9 @@
<button type="button"
class="text-red-500"
title="Bewerbung löschen"
hx-get="{{ path('app_admin_application_delete', { 'uuid': application.uuid }) }}"
hx-get="{{ path('app_admin_application_delete', {
'uuid': application.uuid
}) }}"
hx-target="body"
hx-swap="beforeend">
{{ icon('delete') }}
@@ -1,6 +1,8 @@
{% extends 'htmx_modal.html.twig' %}
{% block title %}Teamer:inneninfo {{ teamer }}{% endblock %}
{% block title %}
Teamer:inneninfo <a href="{{ path('app_administrative_teamer_profile', { 'uuid': teamer.uuid, 'r': returnUrl }) }}">{{ teamer }}</a>
{% endblock %}
{% block content %}
<div class="flex items-start">
@@ -10,7 +12,7 @@
<h2 class="text-lg font-bold">
Zustiege
</h2>
<ul class="pb-8 divide-y divide-gray-200">
<ul class="pb-4 divide-y divide-gray-200">
{% for pickup in teamer.pickups %}
<li class="py-2">
{{ pickup|bpn_pickup_label }}
@@ -22,7 +24,7 @@
{% endfor %}
</ul>
<h2 class="text-lg font-bold">
Letzte Einsätze
Einsätze
</h2>
<ul class="pb-4 divide-y divide-gray-200">
{% for disposition in recentDispositions %}
@@ -38,6 +40,12 @@
</li>
{% endfor %}
</ul>
{% if is_granted('ROLE_ADMIN') %}
<h2 class="text-lg font-bold">
Interne Anmerkungen
</h2>
{{ teamer.remarksInternal | default('-') | nl2br }}
{% endif %}
</div>
<div class="flex flex-col space-y-4">
{% if teamer.photo %}