From d3a0021b7d483d4cd27b4044976be62aff50499b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Fromme?= Date: Mon, 2 Oct 2023 15:07:02 +0200 Subject: [PATCH] WIP: Implement profile editing --- .../Teamer/Profile/License/AddController.php | 9 +- .../Profile/License/DeleteController.php | 35 ++++++ .../Teamer/Profile/SkillsController.php | 41 +++++++ src/Entity/Teamer.php | 13 ++- src/Form/TeamerJobProfileType.php | 78 ++++++++++++++ src/Form/TeamerProfileType.php | 14 --- src/Security/Voter/LicenseVoter.php | 41 +++++++ templates/_partials/_ajax_modal.html.twig | 4 +- .../_partials/_confirmation_modal.html.twig | 6 +- templates/forms.html.twig | 15 ++- templates/teamer/profile/index.html.twig | 2 - templates/teamer/profile/skills.html.twig | 100 ++++++++++++++---- 12 files changed, 311 insertions(+), 47 deletions(-) create mode 100644 src/Controller/Teamer/Profile/License/DeleteController.php create mode 100644 src/Form/TeamerJobProfileType.php create mode 100644 src/Security/Voter/LicenseVoter.php diff --git a/src/Controller/Teamer/Profile/License/AddController.php b/src/Controller/Teamer/Profile/License/AddController.php index 223cdee..62bf02c 100644 --- a/src/Controller/Teamer/Profile/License/AddController.php +++ b/src/Controller/Teamer/Profile/License/AddController.php @@ -10,6 +10,7 @@ use App\Model\AjaxModalResponseDto; use App\Model\UploadSessionDto; use App\Service\Upload\UploadHandler; use Doctrine\ORM\EntityManagerInterface; +use Psr\Log\LoggerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; @@ -20,7 +21,8 @@ class AddController extends AbstractController { public function __construct( private readonly EntityManagerInterface $entityManager, - private readonly UploadHandler $uploadHandler + private readonly UploadHandler $uploadHandler, + private readonly LoggerInterface $logger ) { } @@ -52,6 +54,11 @@ class AddController extends AbstractController $this->entityManager->persist($license); $this->entityManager->flush(); + $this->addFlash('success', 'Die Lizenz wurde hinzugefügt.'); + $this->logger->info('Add license', [ + 'user' => $user->getUserIdentifier(), + ]); + $redirectUrl = $this->generateUrl('app_teamer_profile_skills'); $response->setCloseAndRedirect($redirectUrl); } else { diff --git a/src/Controller/Teamer/Profile/License/DeleteController.php b/src/Controller/Teamer/Profile/License/DeleteController.php new file mode 100644 index 0000000..3e3e289 --- /dev/null +++ b/src/Controller/Teamer/Profile/License/DeleteController.php @@ -0,0 +1,35 @@ +entityManager->remove($license); + $this->entityManager->flush(); + + $this->addFlash('success', 'Die Lizenz wurde gelöscht.'); + $this->logger->info('Delete license', [ + 'user' => $this->getUser()->getUserIdentifier(), + ]); + + return $this->redirectToRoute('app_teamer_profile_skills'); + } +} \ No newline at end of file diff --git a/src/Controller/Teamer/Profile/SkillsController.php b/src/Controller/Teamer/Profile/SkillsController.php index 58ff293..b6bab23 100644 --- a/src/Controller/Teamer/Profile/SkillsController.php +++ b/src/Controller/Teamer/Profile/SkillsController.php @@ -3,6 +3,10 @@ namespace App\Controller\Teamer\Profile; use App\Entity\User; +use App\Form\TeamerJobProfileType; +use App\Repository\JobProfileRepository; +use Doctrine\ORM\EntityManagerInterface; +use Psr\Log\LoggerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -11,6 +15,13 @@ use Symfony\Component\Security\Http\Attribute\IsGranted; class SkillsController extends AbstractController { + public function __construct( + private readonly JobProfileRepository $jobProfileRepository, + private readonly EntityManagerInterface $entityManager, + private readonly LoggerInterface $logger + ) { + } + #[Route('/teamer/profile/skills', name: 'app_teamer_profile_skills')] #[IsGranted('ROLE_TEAMER')] public function index(Request $request): Response @@ -19,8 +30,38 @@ class SkillsController extends AbstractController $user = $this->getUser(); $teamer = $user->getTeamer(); + $jobProfiles = $this->jobProfileRepository->getList(); + $selectableJobProfiles = []; + + foreach ($jobProfiles as $profile) { + $selectableJobProfiles[$profile->getId()] = true; + if (null !== $profile->getRequiredTraining() && false === $teamer->hasTraining($profile->getRequiredTraining())) { + $selectableJobProfiles[$profile->getId()] = false; + } + foreach ($profile->getRequiredLicenses() as $license) { + if (false === $teamer->hasLicenseOfType($license)) { + $selectableJobProfiles[$profile->getId()] = false; + } + } + } + + $form = $this->createForm(TeamerJobProfileType::class, $teamer, ['job_profiles' => $jobProfiles, 'selectable_job_profiles' => $selectableJobProfiles]); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $this->entityManager->flush(); + + $this->addFlash('success', 'Dein Profil wurde aktualisiert.'); + $this->logger->info('Update teamer profile', [ + 'user' => $user->getUserIdentifier(), + ]); + + return $this->redirectToRoute('app_teamer_profile_skills'); + } + return $this->render('teamer/profile/skills.html.twig', [ 'teamer' => $teamer, + 'form' => $form, ]); } } \ No newline at end of file diff --git a/src/Entity/Teamer.php b/src/Entity/Teamer.php index 8ff87df..0260a2a 100644 --- a/src/Entity/Teamer.php +++ b/src/Entity/Teamer.php @@ -86,7 +86,7 @@ class Teamer implements TimestampableEntityInterface #[ORM\OneToMany(mappedBy: 'owner', targetEntity: Availability::class)] private Collection $availabilities; - #[ORM\OneToOne(cascade: ['persist', 'remove'])] + #[ORM\OneToOne(cascade: ['persist', 'remove'], fetch: 'EAGER')] #[Assert\NotNull(message: 'Bitte lade ein Foto von dir hoch', groups: ['profile', 'profile_preflight'])] private ?Upload $photo = null; @@ -465,6 +465,17 @@ class Teamer implements TimestampableEntityInterface return $this; } + public function hasTraining(Training $training): bool + { + foreach ($this->getTrainingAttendances() as $attendance) { + if ($attendance->getTraining() === $training) { + return true; + } + } + + return false; + } + /** * @return Collection */ diff --git a/src/Form/TeamerJobProfileType.php b/src/Form/TeamerJobProfileType.php new file mode 100644 index 0000000..da353aa --- /dev/null +++ b/src/Form/TeamerJobProfileType.php @@ -0,0 +1,78 @@ +add('jobProfiles', EntityType::class, [ + 'label' => false, + 'class' => JobProfile::class, + // Append potential training or license requirements to profile labels + 'choice_label' => function($choice, string $key, mixed $value): TranslatableMessage|string { + $label = $choice->getName(); + $requirements = []; + if (null !== $training = $choice->getRequiredTraining()) { + $requirements[].= $training->getName(); + } + foreach ($choice->getRequiredLicenses() as $license) { + $requirements[] = sprintf('Offizielle %slehrer*innenlizenz', ucfirst($license)); + } + + if ($requirements) { + $label .= sprintf(' (%s: %s)', 1 === count($requirements) ? + 'Voraussetzung' : 'Voraussetzungen', implode(', ', $requirements)); + } + + return $label; + }, + // Set disabled attribute on profiles that require a training or licenses the teamer doesn‘t have + 'choice_attr' => function($choice, string $key, mixed $value) use ($selectableProfiles) { + if (true === $selectableProfiles[$choice->getId()]) { + return []; + } + + return ['disabled' => 'disabled']; + }, + 'expanded' => true, + 'multiple' => true, + ]) + ->add('status', ChoiceType::class, [ + 'label' => 'Status', + 'choices' => [ + 'Neuteamer' => Teamer::STATUS_NEW, + 'Bestandsteamer' => Teamer::STATUS_EXISTING, + ], + ]) + ->add('remarks', TextareaType::class, [ + 'label' => 'Wünsche/Anmerkungen', + 'required' => false, + 'attr' => [ + 'rows' => 3, + ], + ]) + ; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => Teamer::class, + 'job_profiles' => [], + 'selectable_job_profiles' => [], + ]); + } +} \ No newline at end of file diff --git a/src/Form/TeamerProfileType.php b/src/Form/TeamerProfileType.php index 94f876c..a0adc3c 100644 --- a/src/Form/TeamerProfileType.php +++ b/src/Form/TeamerProfileType.php @@ -63,13 +63,6 @@ class TeamerProfileType extends AbstractType 'label' => 'Krankenversicherung', 'required' => false, ]) - ->add('remarks', TextareaType::class, [ - 'label' => 'Wünsche/Anmerkungen', - 'required' => false, - 'attr' => [ - 'rows' => 3, - ], - ]) ->add('address', AddressType::class, [ 'label' => false, ]) @@ -99,13 +92,6 @@ class TeamerProfileType extends AbstractType 'XXL' => 'XXL', ], ]) - ->add('status', ChoiceType::class, [ - 'label' => 'Status', - 'choices' => [ - 'Neuteamer' => Teamer::STATUS_NEW, - 'Bestandsteamer' => Teamer::STATUS_EXISTING, - ], - ]) ; } diff --git a/src/Security/Voter/LicenseVoter.php b/src/Security/Voter/LicenseVoter.php new file mode 100644 index 0000000..c7aa6fd --- /dev/null +++ b/src/Security/Voter/LicenseVoter.php @@ -0,0 +1,41 @@ +security->isGranted('ROLE_ADMINISTRATIVE')) { + return true; + } + + /** @var License $license */ + $license = $subject; + /** @var User $user */ + $user = $this->security->getUser(); + + return $license->getTeamer() === $user->getTeamer(); + } +} \ No newline at end of file diff --git a/templates/_partials/_ajax_modal.html.twig b/templates/_partials/_ajax_modal.html.twig index 7396c2a..d7d9540 100644 --- a/templates/_partials/_ajax_modal.html.twig +++ b/templates/_partials/_ajax_modal.html.twig @@ -3,8 +3,8 @@ {{ stimulus_controller('ajax-modal') }} >
-
-
+
+
-
diff --git a/templates/forms.html.twig b/templates/forms.html.twig index 95d5243..9668e92 100644 --- a/templates/forms.html.twig +++ b/templates/forms.html.twig @@ -67,6 +67,19 @@ {%- endif -%} {%- endblock textarea_widget -%} +{%- block checkbox_widget -%} + {%- set attr = attr|merge({'class': (attr.class|default('') ~ ' h-4 w-4 rounded border-gray-300 text-primary')|trim }) -%} + {%- if errors|length -%} + {%- set attr = attr|merge({'class': attr.class|default('') ~ ' ring-red-500 focus:ring-red-500' }) -%} + {% else %} + {%- set attr = attr|merge({'class': attr.class|default('') ~ ' 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 -%} + +{%- endblock checkbox_widget -%} + {%- 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 sm:text-sm sm:leading-6')|trim }) -%} {%- if errors|length -%} @@ -99,7 +112,7 @@
- + {{- form_widget(child) -}}
{% endfor -%} diff --git a/templates/teamer/profile/index.html.twig b/templates/teamer/profile/index.html.twig index f780f15..5cadc49 100644 --- a/templates/teamer/profile/index.html.twig +++ b/templates/teamer/profile/index.html.twig @@ -75,7 +75,6 @@ Persönliche Daten
- {{ form_row(form.status) }} {{ form_row(form.salutation) }} {{ form_row(form.academicTitle) }} {{ form_row(form.firstName) }} @@ -127,7 +126,6 @@ {{ form_row(form.healthInsuranceCompany) }} {{ form_row(form.language) }} {{ form_row(form.size) }} - {{ form_row(form.remarks) }}
diff --git a/templates/teamer/profile/skills.html.twig b/templates/teamer/profile/skills.html.twig index f2df945..6517a65 100644 --- a/templates/teamer/profile/skills.html.twig +++ b/templates/teamer/profile/skills.html.twig @@ -4,32 +4,86 @@ {% block content %}

- Lizenzen + Ausbildung

-
- {% for license in teamer.licenses %} -
- {{ icon(license.type, 'w-5 h-5') }} - {{ license.date|date('m.Y') }} - {% if is_granted('DOWNLOAD', license.certificate) %} - - {{ icon('download', 'w-4 h-4') }} - - {% endif %} -
+
    + {% for attendance in teamer.trainingAttendances %} +
  • + {{ attendance.training.name }} {{ attendance.date|date('m/Y') }} +
  • {% else %} -
    - Du hast bisher keine Lizenzen hinterlegt -
    +
  • + Du hast bisher an keinen Fortbildungen teilgenommen +
  • {% endfor %} +
+ +
+

+ Lizenzen +

+
    + {% for license in teamer.licenses %} +
  • +
    + {{ icon(license.type, 'w-5 h-5') }} + {{ license.date|date('m/Y') }} + {% if is_granted('DOWNLOAD', license.certificate) %} + + {{ icon('download', 'w-4 h-4') }} + + {% endif %} + {% if is_granted('DELETE', license) %} + + {% endif %} +
    +
  • + {% else %} +
    + Du hast bisher keine Lizenzen hinterlegt +
    + {% endfor %} +
+
- + {{ form_rest(form) }} + {{ form_end(form) }} {% endblock %} \ No newline at end of file