From abced8c924f0a623d14424903099ee6127e187cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Fromme?= Date: Mon, 9 Oct 2023 14:42:37 +0200 Subject: [PATCH] WIP: Implement application process --- migrations/Version20231009102628.php | 31 ++++++ migrations/Version20231009104620.php | 31 ++++++ .../Teamer/Availability/DeleteController.php | 53 +++++++++ .../Admin/Teamer/AvailabilityController.php | 6 ++ .../Teamer/ApplicationController.php | 56 ++++++++++ .../Teamer/AssignmentController.php | 21 ++++ src/Controller/Teamer/IndexController.php | 29 ++++- .../Profile/Availability/IndexController.php | 33 +----- src/Controller/Traits/ReturnUrlTrait.php | 15 +++ src/Entity/Application.php | 28 ++++- src/Entity/Assignment.php | 36 +++++-- src/Entity/Teamer.php | 5 + src/Form/AssignmentType.php | 19 ++-- src/Form/TeamerApplicationType.php | 56 ++++++++++ src/Menu/TeamerMenuBuilder.php | 8 ++ src/Model/BenefitsDto.php | 20 ---- src/Service/Teamer/AvailabilityProcessor.php | 46 ++++++++ src/Twig/AppExtension.php | 3 +- src/Twig/AppRuntime.php | 50 +++++---- templates/admin/assignment/_form.html.twig | 5 +- templates/admin/teamer/availability.html.twig | 39 +++++++ templates/admin/teamer/skills.html.twig | 8 +- templates/paginator/sliding.html.twig | 80 +++++++------- templates/teamer/application.html.twig | 101 ++++++++++++++++++ templates/teamer/assignment.html.twig | 84 +++++++++++++++ templates/teamer/index.html.twig | 77 ++++++++++++- 26 files changed, 802 insertions(+), 138 deletions(-) create mode 100644 migrations/Version20231009102628.php create mode 100644 migrations/Version20231009104620.php create mode 100644 src/Controller/Admin/Teamer/Availability/DeleteController.php create mode 100644 src/Controller/Teamer/ApplicationController.php create mode 100644 src/Controller/Teamer/AssignmentController.php create mode 100644 src/Controller/Traits/ReturnUrlTrait.php create mode 100644 src/Form/TeamerApplicationType.php delete mode 100644 src/Model/BenefitsDto.php create mode 100644 src/Service/Teamer/AvailabilityProcessor.php create mode 100644 templates/teamer/application.html.twig create mode 100644 templates/teamer/assignment.html.twig diff --git a/migrations/Version20231009102628.php b/migrations/Version20231009102628.php new file mode 100644 index 0000000..6000304 --- /dev/null +++ b/migrations/Version20231009102628.php @@ -0,0 +1,31 @@ +addSql('ALTER TABLE assignment CHANGE benefits benefits LONGTEXT DEFAULT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE assignment CHANGE benefits benefits JSON NOT NULL COMMENT \'(DC2Type:json)\''); + } +} diff --git a/migrations/Version20231009104620.php b/migrations/Version20231009104620.php new file mode 100644 index 0000000..d45f348 --- /dev/null +++ b/migrations/Version20231009104620.php @@ -0,0 +1,31 @@ +addSql('ALTER TABLE assignment ADD show_available_dispositions TINYINT(1) NOT NULL, CHANGE available available_dispositions INT DEFAULT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE assignment DROP show_available_dispositions, CHANGE available_dispositions available INT DEFAULT NULL'); + } +} diff --git a/src/Controller/Admin/Teamer/Availability/DeleteController.php b/src/Controller/Admin/Teamer/Availability/DeleteController.php new file mode 100644 index 0000000..611cee5 --- /dev/null +++ b/src/Controller/Admin/Teamer/Availability/DeleteController.php @@ -0,0 +1,53 @@ + 'uuid'])] + Teamer $teamer, + #[MapEntity(mapping: ['availability_id' => 'id'])] + Availability $availability, + Request $request + ): Response { + if (null === $availability->getOwner()) { + $teamer->removeAvailability($availability); + } elseif ($teamer === $availability->getOwner()) { + $this->entityManager->remove($availability); + } + + $this->entityManager->flush(); + + $this->addFlash('success', 'Der Zeitraum wurde entfernt/gelöscht.'); + $this->logger->info('Delete availability', [ + 'teamer' => $teamer->getUuid(), + ]); + + $redirectUrl = $this->getReturnUrl($request, 'app_admin_teamer_profile', ['uuid' => $teamer->getUuid()]); + + return $this->redirect($redirectUrl); + } +} \ No newline at end of file diff --git a/src/Controller/Admin/Teamer/AvailabilityController.php b/src/Controller/Admin/Teamer/AvailabilityController.php index 90378d3..4b60628 100644 --- a/src/Controller/Admin/Teamer/AvailabilityController.php +++ b/src/Controller/Admin/Teamer/AvailabilityController.php @@ -3,6 +3,7 @@ namespace App\Controller\Admin\Teamer; use App\Entity\Teamer; +use App\Service\Teamer\AvailabilityProcessor; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; @@ -14,8 +15,13 @@ class AvailabilityController extends AbstractController #[IsGranted('ROLE_ADMINISTRATIVE')] public function index(Teamer $teamer): Response { + $processor = new AvailabilityProcessor(); + $availabilities = $teamer->getAvailabilities()->toArray(); + $groupedAvailabilities = $processor->groupRecords($availabilities); + return $this->render('admin/teamer/availability.html.twig', [ 'teamer' => $teamer, + 'groupedAvailabilities' => $groupedAvailabilities, ]); } } \ No newline at end of file diff --git a/src/Controller/Teamer/ApplicationController.php b/src/Controller/Teamer/ApplicationController.php new file mode 100644 index 0000000..1250ab5 --- /dev/null +++ b/src/Controller/Teamer/ApplicationController.php @@ -0,0 +1,56 @@ +getUser(); + $teamer = $user->getTeamer(); + $application = new Application($assignment, $teamer); + + $form = $this->createForm(TeamerApplicationType::class, $application); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $this->entityManager->persist($application); + $this->entityManager->flush(); + + $this->addFlash('success', 'Deine Bewerbung wurde entgegengenommen'); + $this->logger->info('Create application', [ + 'teamer' => $teamer->getUuid(), + 'application' => $application->getUuid(), + ]); + + return $this->redirectToRoute('app_teamer_index'); + } + + return $this->render('teamer/application.html.twig', [ + 'assignment' => $assignment, + 'teamer' => $teamer, + 'form' => $form, + ]); + } +} \ No newline at end of file diff --git a/src/Controller/Teamer/AssignmentController.php b/src/Controller/Teamer/AssignmentController.php new file mode 100644 index 0000000..7782858 --- /dev/null +++ b/src/Controller/Teamer/AssignmentController.php @@ -0,0 +1,21 @@ +render('teamer/assignment.html.twig', [ + 'assignment' => $assignment, + ]); + } +} \ No newline at end of file diff --git a/src/Controller/Teamer/IndexController.php b/src/Controller/Teamer/IndexController.php index 1964ce9..c72f536 100644 --- a/src/Controller/Teamer/IndexController.php +++ b/src/Controller/Teamer/IndexController.php @@ -3,7 +3,10 @@ namespace App\Controller\Teamer; use App\Entity\User; +use App\Repository\AssignmentRepository; +use Knp\Component\Pager\PaginatorInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Security\Http\Attribute\IsGranted; @@ -11,12 +14,16 @@ use Symfony\Component\Validator\Validator\ValidatorInterface; class IndexController extends AbstractController { - public function __construct(private readonly ValidatorInterface $validator) - {} + public function __construct( + private readonly ValidatorInterface $validator, + private readonly PaginatorInterface $paginator, + private readonly AssignmentRepository $assignmentRepository + ) { + } #[Route('/teamer', name: 'app_teamer_index')] #[IsGranted('ROLE_TEAMER')] - public function index(): Response + public function index(Request $request): Response { /** @var User $user */ $user = $this->getUser(); @@ -25,7 +32,23 @@ class IndexController extends AbstractController // Validate teamer data to show missing data right away $errors = $this->validator->validate($teamer, null, ['profile_preflight']); + $query = $this + ->assignmentRepository + ->getListQuery() + ; + + $pagination = $this->paginator->paginate( + $query, + $request->query->getInt('page', 1), + 10, + [ + 'defaultSortFieldName' => 'assignment.dateFrom', + 'defaultSortDirection' => 'asc', + ] + ); + return $this->render('teamer/index.html.twig', [ + 'pagination' => $pagination, 'errors' => $errors, ]); } diff --git a/src/Controller/Teamer/Profile/Availability/IndexController.php b/src/Controller/Teamer/Profile/Availability/IndexController.php index 57a93a7..1843393 100644 --- a/src/Controller/Teamer/Profile/Availability/IndexController.php +++ b/src/Controller/Teamer/Profile/Availability/IndexController.php @@ -2,10 +2,9 @@ namespace App\Controller\Teamer\Profile\Availability; -use App\Entity\Availability; use App\Entity\User; use App\Repository\AvailabilityRepository; -use Carbon\Carbon; +use App\Service\Teamer\AvailabilityProcessor; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; @@ -24,39 +23,17 @@ class IndexController extends AbstractController $user = $this->getUser(); $teamer = $user->getTeamer(); + $processor = new AvailabilityProcessor(); + $availabilities = $teamer->getAvailabilities()->toArray(); - $selectedAvailabilities = $this->groupRecords($availabilities); + $selectedAvailabilities = $processor->groupRecords($availabilities); $availabilities = $this->availabilityRepository->getSelectableForTeamer($teamer); - $selectableAvailabilities = $this->groupRecords($availabilities); + $selectableAvailabilities = $processor->groupRecords($availabilities); return $this->render('teamer/profile/availability/index.html.twig', [ 'selectedAvailabilities' => $selectedAvailabilities, 'selectableAvailabilities' => $selectableAvailabilities, ]); } - - private function groupRecords(array $records): array - { - $selectableAvailabilities = []; - - foreach ($records as $record) { - /** @var Availability $record */ - $dateFrom = $record->getDateFrom(); - $year = $dateFrom->format('Y'); - $month = (new Carbon($dateFrom))->locale('de_DE')->monthName; - - if (false === isset($selectableAvailabilities[$year])) { - $selectableAvailabilities[$year] = []; - } - - if (false === isset($selectableAvailabilities[$year][$month])) { - $selectableAvailabilities[$year][$month] = []; - } - - $selectableAvailabilities[$year][$month][] = $record; - } - - return $selectableAvailabilities; - } } \ No newline at end of file diff --git a/src/Controller/Traits/ReturnUrlTrait.php b/src/Controller/Traits/ReturnUrlTrait.php new file mode 100644 index 0000000..abf7e36 --- /dev/null +++ b/src/Controller/Traits/ReturnUrlTrait.php @@ -0,0 +1,15 @@ +generateUrl($defaultRoute, $parameters); + + return rawurldecode($request->query->get('r', $defaultUrl)); + } +} diff --git a/src/Entity/Application.php b/src/Entity/Application.php index 6f35758..1405e61 100644 --- a/src/Entity/Application.php +++ b/src/Entity/Application.php @@ -6,37 +6,57 @@ use App\Entity\Traits\TimestampableEntity; use App\Repository\ApplicationRepository; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Uid\Uuid; #[ORM\Entity(repositoryClass: ApplicationRepository::class)] class Application implements TimestampableEntityInterface { use TimestampableEntity; + public const STATUS_NEW = 'new'; + public const STATUS_PENDING = 'pending'; + #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column] private ?int $id = null; - #[ORM\ManyToOne(inversedBy: 'applications')] - private ?Assignment $assignment = null; + #[ORM\Column(type: 'string', length: 36, unique: true)] + private string $uuid; #[ORM\ManyToOne(inversedBy: 'applications')] - private ?Teamer $teamer = null; + private ?Assignment $assignment; + + #[ORM\ManyToOne(inversedBy: 'applications')] + private ?Teamer $teamer; #[ORM\Column(type: Types::TEXT, nullable: true)] private ?string $requests = null; #[ORM\Column(length: 64)] - private ?string $status = null; + private ?string $status; #[ORM\Column(type: Types::TEXT, nullable: true)] private ?string $remarks = null; + public function __construct(Assignment $assignment, Teamer $teamer) + { + $this->uuid = Uuid::v4(); + $this->assignment = $assignment; + $this->teamer = $teamer; + $this->status = static::STATUS_NEW; + } + public function getId(): ?int { return $this->id; } + public function getUuid(): string + { + return $this->uuid; + } + public function getAssignment(): ?Assignment { return $this->assignment; diff --git a/src/Entity/Assignment.php b/src/Entity/Assignment.php index 731038d..dffd87e 100644 --- a/src/Entity/Assignment.php +++ b/src/Entity/Assignment.php @@ -4,7 +4,6 @@ namespace App\Entity; use App\Entity\Traits\BlameableEntity; use App\Entity\Traits\TimestampableEntity; -use App\Model\BenefitsDto; use App\Repository\AssignmentRepository; use Carbon\CarbonPeriod; use Doctrine\Common\Collections\ArrayCollection; @@ -38,7 +37,10 @@ class Assignment implements BlameableEntityInterface, TimestampableEntityInterfa #[ORM\Column(nullable: true)] #[Assert\Range(min: 1, minMessage: 'Die Mindestanzahl ist 1')] - private ?int $available = 1; + private ?int $availableDispositions = 1; + + #[ORM\Column] + private ?bool $showAvailableDispositions = false; #[ORM\Column(type: Types::DATE_IMMUTABLE, nullable: true)] private ?\DateTimeImmutable $dateFrom = null; @@ -53,8 +55,8 @@ class Assignment implements BlameableEntityInterface, TimestampableEntityInterfa #[ORM\Column(nullable: true)] private ?int $pickup = null; - #[ORM\Column] - private array $benefits; + #[ORM\Column(type: Types::TEXT, nullable: true)] + private ?string $benefits; #[ORM\Column(type: Types::TEXT, nullable: true)] private ?string $remarks = null; @@ -78,7 +80,7 @@ class Assignment implements BlameableEntityInterface, TimestampableEntityInterfa $this->fees = new ArrayCollection(); $this->applications = new ArrayCollection(); $this->dispositions = new ArrayCollection(); - $this->benefits = BenefitsDto::$defaults; + $this->benefits = "Anreise im E&P Reisebus\nUnterkunft\nVerpflegung\nSkipass\n"; } public function getId(): ?int @@ -115,14 +117,26 @@ class Assignment implements BlameableEntityInterface, TimestampableEntityInterfa return $this; } - public function getAvailable(): ?int + public function getAvailableDispositions(): ?int { - return $this->available; + return $this->availableDispositions; } - public function setAvailable(?int $available): static + public function setAvailableDispositions(?int $availableDispositions): static { - $this->available = $available; + $this->availableDispositions = $availableDispositions; + + return $this; + } + + public function isShowAvailableDispositions(): ?bool + { + return $this->showAvailableDispositions; + } + + public function setShowAvailableDispositions(bool $showAvailableDispositions): static + { + $this->showAvailableDispositions = $showAvailableDispositions; return $this; } @@ -184,12 +198,12 @@ class Assignment implements BlameableEntityInterface, TimestampableEntityInterfa return $this; } - public function getBenefits(): array + public function getBenefits(): ?string { return $this->benefits; } - public function setBenefits(array $benefits): static + public function setBenefits(?string $benefits): static { $this->benefits = $benefits; diff --git a/src/Entity/Teamer.php b/src/Entity/Teamer.php index c6d6bd4..d315220 100644 --- a/src/Entity/Teamer.php +++ b/src/Entity/Teamer.php @@ -692,6 +692,11 @@ class Teamer implements TimestampableEntityInterface return $this; } + public function hasPickup(int $pickupId): bool + { + return in_array($pickupId, $this->getPickups()); + } + public function getLanguage(): ?string { return $this->language; diff --git a/src/Form/AssignmentType.php b/src/Form/AssignmentType.php index 1b90075..d459f86 100644 --- a/src/Form/AssignmentType.php +++ b/src/Form/AssignmentType.php @@ -7,11 +7,10 @@ use App\Entity\Destination; use App\Entity\Fee; use App\Entity\JobProfile; use App\Entity\User; -use App\Model\BenefitsDto; use Doctrine\ORM\EntityRepository; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\AbstractType; -use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\IntegerType; use Symfony\Component\Form\Extension\Core\Type\TextareaType; use Symfony\Component\Form\FormBuilderInterface; @@ -22,8 +21,12 @@ class AssignmentType extends AbstractType public function buildForm(FormBuilderInterface $builder, array $options): void { $builder - ->add('available', IntegerType::class, [ - 'label' => 'Anzahl', + ->add('availableDispositions', IntegerType::class, [ + 'label' => 'zu vergeben', + 'required' => false, + ]) + ->add('showAvailableDispositions', CheckboxType::class, [ + 'label' => 'Anzahl anzeigen', 'required' => false, ]) ->add('destination', AutocompleteEntityType::class, [ @@ -56,12 +59,12 @@ class AssignmentType extends AbstractType 'required' => false, 'placeholder' => 'Keine Busbegleitung', ]) - ->add('benefits', ChoiceType::class, [ + ->add('benefits', TextareaType::class, [ 'label' => 'Benefits', 'required' => false, - 'multiple' => true, - 'expanded' => true, - 'choices' => array_flip(BenefitsDto::$values), + 'attr' => [ + 'rows' => 5, + ], ]) ->add('fees', EntityType::class, [ 'label' => 'Honorar(e)', diff --git a/src/Form/TeamerApplicationType.php b/src/Form/TeamerApplicationType.php new file mode 100644 index 0000000..750f742 --- /dev/null +++ b/src/Form/TeamerApplicationType.php @@ -0,0 +1,56 @@ +addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) { + /** @var Application $application */ + $application = $event->getData(); + + if (null === $application) { + return; + } + + $assignment = $application->getAssignment(); + + if (null === $assignment->getPickup()) { + return; + } + + $teamer = $application->getTeamer(); + + if (in_array($assignment->getPickup(), $teamer->getPickups())) { + return; + } + + $event->getForm()->add('confirmPickup', CheckboxType::class, [ + 'label' => 'Ich bestätige den abweichenden Buszustieg', + 'mapped' => false, + 'constraints' => [ + new IsTrue([ + 'message' => 'Deine Bestätigung ist erforderlich', + ]), + ], + ]); + }); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => Application::class, + ]); + } +} \ No newline at end of file diff --git a/src/Menu/TeamerMenuBuilder.php b/src/Menu/TeamerMenuBuilder.php index e11451e..7021c7c 100644 --- a/src/Menu/TeamerMenuBuilder.php +++ b/src/Menu/TeamerMenuBuilder.php @@ -13,6 +13,14 @@ class TeamerMenuBuilder extends AbstractMenuBuilder 'route' => 'app_teamer_index', 'title' => 'Einsatzübersicht', 'icon' => 'calendar', + 'hideChildren' => true, + 'children' => [ + [ + 'route' => 'app_teamer_assignment', + 'title' => 'Einsatzdetails', + 'routeParameters' => $this->getDefaultRouteParameters('uuid'), + ] + ], ], [ 'route' => false, diff --git a/src/Model/BenefitsDto.php b/src/Model/BenefitsDto.php deleted file mode 100644 index 902c1f7..0000000 --- a/src/Model/BenefitsDto.php +++ /dev/null @@ -1,20 +0,0 @@ - 'Anreise im E&P Reisebus', - 'unterkunft' => 'Unterkunft', - 'verpflegung' => 'Verpflegung', - 'skipass' => 'Skipass', - ]; -} \ No newline at end of file diff --git a/src/Service/Teamer/AvailabilityProcessor.php b/src/Service/Teamer/AvailabilityProcessor.php new file mode 100644 index 0000000..27d2aa1 --- /dev/null +++ b/src/Service/Teamer/AvailabilityProcessor.php @@ -0,0 +1,46 @@ + [ + * 'Januar' => [ + * ... + * ], + * 'Juni' => [ + * ... + * ], + * ], + * ] + */ + public function groupRecords(array $records): array + { + $availabilities = []; + + foreach ($records as $record) { + /** @var Availability $record */ + $dateFrom = $record->getDateFrom(); + $year = $dateFrom->format('Y'); + $month = (new Carbon($dateFrom))->locale('de_DE')->monthName; + + if (false === isset($availabilities[$year])) { + $availabilities[$year] = []; + } + + if (false === isset($availabilities[$year][$month])) { + $availabilities[$year][$month] = []; + } + + $availabilities[$year][$month][] = $record; + } + + return $availabilities; + } +} \ No newline at end of file diff --git a/src/Twig/AppExtension.php b/src/Twig/AppExtension.php index 8f5a012..ffbcd15 100644 --- a/src/Twig/AppExtension.php +++ b/src/Twig/AppExtension.php @@ -11,6 +11,7 @@ class AppExtension extends AbstractExtension public function getFilters(): array { return [ + new TwigFilter('nl2list', [AppRuntime::class, 'nl2List'], ['is_safe' => ['html']]), new TwigFilter('file_size', [AppRuntime::class, 'formatBytes']), new TwigFilter('file_icon', [AppRuntime::class, 'fileIconFilter'], ['is_safe' => ['html']]), new TwigFilter('date_diff', [AppRuntime::class, 'dateDiffForHumans']), @@ -19,7 +20,6 @@ class AppExtension extends AbstractExtension new TwigFilter('teamer_status_label', [AppRuntime::class, 'teamerStatusLabel']), new TwigFilter('bpn_country_label', [AppRuntime::class, 'bpnCountryLabel']), new TwigFilter('bpn_pickup_label', [AppRuntime::class, 'bpnPickupLabel']), - new TwigFilter('bpn_destination_label', [AppRuntime::class, 'bpnDestinationLabel']), ]; } @@ -28,6 +28,7 @@ class AppExtension extends AbstractExtension return [ new TwigFunction('icon', [AppRuntime::class, 'renderIcon'], ['needs_environment' => true, 'is_safe' => ['html']]), new TwigFunction('is_current_route', [AppRuntime::class, 'isCurrentRoute']), + new TwigFunction('return_url', [AppRuntime::class, 'getEncodedReturnUrl']), new TwigFunction('qa_attribute', [AppRuntime::class, 'renderQaAttribute'], ['is_safe' => ['html']]), ]; } diff --git a/src/Twig/AppRuntime.php b/src/Twig/AppRuntime.php index 69e2577..42ebeda 100644 --- a/src/Twig/AppRuntime.php +++ b/src/Twig/AppRuntime.php @@ -3,9 +3,7 @@ namespace App\Twig; use App\BusProNet\DataProvider\CountryDataProvider; -use App\BusProNet\DataProvider\HotelDataProvider; use App\BusProNet\DataProvider\PickupDataProvider; -use App\BusProNet\Model\Country; use App\Entity\Teamer; use Carbon\Carbon; use Symfony\Component\HttpFoundation\RequestStack; @@ -16,12 +14,11 @@ use Twig\Extra\Intl\IntlExtension; class AppRuntime implements RuntimeExtensionInterface { public function __construct( - private readonly RequestStack $requestStack, - private readonly IntlExtension $intlExtension, + private readonly RequestStack $requestStack, + private readonly IntlExtension $intlExtension, private readonly CountryDataProvider $countries, - private readonly PickupDataProvider $pickups, - private readonly HotelDataProvider $hotels, - private readonly string $environment + private readonly PickupDataProvider $pickups, + private readonly string $environment ) { } @@ -47,7 +44,7 @@ class AppRuntime implements RuntimeExtensionInterface return 'unbekannt'; } - return 'nationality' === $property ? $country->getNationality() : $country->getName(); + return 'nationality' === $property ? $country->getNationality() : $country->getName(); } public function bpnPickupLabel(int $id): string @@ -61,17 +58,6 @@ class AppRuntime implements RuntimeExtensionInterface return $pickup->getCity(); } - public function bpnDestinationLabel(int $id): string - { - $hotel = $this->hotels->get($id); - - if (null === $hotel) { - return 'unbekannt'; - } - - return sprintf('%s, %s', $hotel->getName(), $hotel->getCity() ?? '-'); - } - public function fileIconFilter(Environment $environment, string $mimeType, ?string $classes = 'w-4 h-4'): string { $icon = match ($mimeType) { @@ -89,7 +75,7 @@ class AppRuntime implements RuntimeExtensionInterface $size = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; $factor = floor((strlen($bytes) - 1) / 3); - return sprintf("%.{$precision}f", $bytes / (1024 ** $factor)).@$size[$factor]; + return sprintf("%.{$precision}f", $bytes / (1024 ** $factor)) . @$size[$factor]; } public function formatMoney(int $amount): string @@ -108,6 +94,19 @@ class AppRuntime implements RuntimeExtensionInterface ]); } + public function nl2List(string $content, string $class = 'list-disc pl-4'): string + { + $items = explode(PHP_EOL, $content); + + $list = ' diff --git a/templates/paginator/sliding.html.twig b/templates/paginator/sliding.html.twig index 539e9c6..0a38dc1 100644 --- a/templates/paginator/sliding.html.twig +++ b/templates/paginator/sliding.html.twig @@ -1,42 +1,44 @@ -
-
-
-

{{ 'paginator.info'|trans({ '{from}': firstItemNumber, '{to}': lastItemNumber, '{total}': totalCount })|raw }}

-
-
- +
- +{% endif %} \ No newline at end of file diff --git a/templates/teamer/application.html.twig b/templates/teamer/application.html.twig new file mode 100644 index 0000000..3b61ac8 --- /dev/null +++ b/templates/teamer/application.html.twig @@ -0,0 +1,101 @@ +{% extends 'teamer/layout.html.twig' %} + +{% block title %}Bewerbung{% endblock %} + +{% block content %} +

+ Bewerbung +

+
+
+
+ Jobprofil +
+
+ {{ assignment.jobProfile.name }} +
+
+ Destination +
+
+ {{ assignment.destination.product }} +
+ {{ assignment.destination.hotel }} +
+
+ Einsatztermin +
+
+ {{ assignment.totalPeriod.start|date('d.m.Y') }} - {{ assignment.totalPeriod.end|date('d.m.Y') }} +
+ {% if assignment.pickup and assignment.pickupDate %} +
+ Busabfahrt +
+
+ {{ assignment.pickupDate|date('d.m.Y') }} +
+
+ Busbegleitung +
+
+ ab {{ assignment.pickup|bpn_pickup_label }} +
+ {% endif %} +
+ Du bekommst +
+
+ {{ assignment.benefits|nl2list }} +
+
+ Honorar +
+
+ {% for fee in assignment.fees %} + {{ fee.name }} {{ fee.value|format_money }} + {% endfor %} +
+
+
+

+ Voraussetzungen für diesen Einsatz +

+
    + {% if assignment.pickup and assignment.pickupDate %} +
  • +
    + Busbegleitung ab {{ assignment.pickup|bpn_pickup_label }} am {{ assignment.pickupDate|date('d.m.Y') }} + {% if teamer.hasPickup(assignment.pickup) %} + {{ icon('check', 'w-5 h-5 text-emerald-500') }} + {% else %} + {{ icon('close', 'w-5 h-5 text-red-500') }} + {% endif %} +
    +
  • + {% endif %} + {% for training in assignment.jobProfile.requiredTrainings %} +
  • +
    + {{ training.name }} absolviert + {% if teamer.trainingAttendance(training) %} + {{ icon('check', 'w-5 h-5 text-emerald-500') }} + {% else %} + {{ icon('close', 'w-5 h-5 text-red-500') }} + {% endif %} +
    +
  • + {% endfor %} +
+ {{ form_start(form) }} + {% if form.confirmPickup is defined %} + {{ form_row(form.confirmPickup) }} + {% endif %} + + {{ form_rest(form) }} + {{ form_end(form) }} +
+
+{% endblock %} \ No newline at end of file diff --git a/templates/teamer/assignment.html.twig b/templates/teamer/assignment.html.twig new file mode 100644 index 0000000..7c3642c --- /dev/null +++ b/templates/teamer/assignment.html.twig @@ -0,0 +1,84 @@ +{% extends 'teamer/layout.html.twig' %} + +{% block title %}Einsatzübersicht{% endblock %} + +{% block content %} +

+ Einsatzübersicht +

+
+
+
+ Jobprofil +
+
+ {{ assignment.jobProfile.name }} +
+
+ Destination +
+
+ {{ assignment.destination.product }} +
+ {{ assignment.destination.hotel }} +
+
+ Einsatztermin +
+
+ {{ assignment.totalPeriod.start|date('d.m.Y') }} - {{ assignment.totalPeriod.end|date('d.m.Y') }} +
+ {% if assignment.pickup and assignment.pickupDate %} +
+ Busabfahrt +
+
+ {{ assignment.pickupDate|date('d.m.Y') }} +
+
+ Busbegleitung +
+
+ ab {{ assignment.pickup|bpn_pickup_label }} +
+ {% endif %} +
+ Du bekommst +
+
+ {{ assignment.benefits|nl2list }} +
+
+ Honorar +
+
+ {% for fee in assignment.fees %} + {{ fee.name }} {{ fee.value|format_money }} + {% endfor %} +
+
+
+

+ Voraussetzungen für diesen Einsatz +

+
    + {% if assignment.pickup and assignment.pickupDate %} +
  • + Busbegleitung ab {{ assignment.pickup|bpn_pickup_label }} am {{ assignment.pickupDate|date('d.m.Y') }} +
  • + {% endif %} + {% for training in assignment.jobProfile.requiredTrainings %} +
  • + {{ training.name }} absolviert +
  • + {% endfor %} +
+

+ Erfüllst du mehrere oder alle Voraussetzungen für diesen Einsatz? +

+ + Hier bewerben! + +
+
+{% endblock %} \ No newline at end of file diff --git a/templates/teamer/index.html.twig b/templates/teamer/index.html.twig index c497fc7..3ac9592 100644 --- a/templates/teamer/index.html.twig +++ b/templates/teamer/index.html.twig @@ -1,8 +1,11 @@ {% extends 'teamer/layout.html.twig' %} -{% block title %}Mein Dashboard{% endblock %} +{% block title %}Einsatzübersicht{% endblock %} {% block content %} +

+ Einsatzübersicht +

{% if errors|length %}
{% endif %} +
+
+ + + + + + + + + + + + + {% for assignment in pagination %} + + + + + + + + + {% else %} + + + + {% endfor %} + +
+ {{ knp_pagination_sortable(pagination, 'Einsatz­beginn', 'assignment.dateFrom') }} + + {{ knp_pagination_sortable(pagination, 'Einsatz­ende', 'assignment.dateTo') }} + + {{ knp_pagination_sortable(pagination, 'Jobprofil', 'assignment.jobProfile') }} + + {{ knp_pagination_sortable(pagination, 'Destination', 'destination.dateFrom') }} + + {{ knp_pagination_sortable(pagination, 'Busbegleitung ab', 'assignment.pickup') }} +
+ {% set dateFrom = assignment.dateFrom ? assignment.dateFrom : assignment.destination.dateFrom %} + {{ dateFrom|date('d.m.Y') }} + + {% set dateTo = assignment.dateTo ? assignment.dateTo : assignment.destination.dateTo %} + {{ dateTo|date('d.m.Y') }} + + {{ assignment.jobProfile.name }} + + {{ assignment.destination.product }} +
+ {{ assignment.destination.hotel }} +
+ {{ assignment.pickup ? assignment.pickup|bpn_pickup_label|default('-') : '-' }} +
+ {{ assignment.pickupDate ? assignment.pickupDate|date('d.m.Y') : '' }} +
+ +
+
+ Keine Daten +
+
+ {{ knp_pagination_render(pagination) }} +
+
+ + Neu + {% endblock %} \ No newline at end of file