WIP: Implement application process
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\Admin\Teamer\Availability;
|
||||
|
||||
use App\Controller\Traits\ReturnUrlTrait;
|
||||
use App\Entity\Availability;
|
||||
use App\Entity\Teamer;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
|
||||
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;
|
||||
|
||||
class DeleteController extends AbstractController
|
||||
{
|
||||
use ReturnUrlTrait;
|
||||
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly LoggerInterface $logger
|
||||
) {
|
||||
}
|
||||
|
||||
#[Route('/admin/teamer/availability/delete/{teamer_uuid}/{availability_id}', name: 'app_admin_teamer_availability_delete')]
|
||||
#[IsGranted('ROLE_ADMINISTRATIVE')]
|
||||
public function index(
|
||||
#[MapEntity(mapping: ['teamer_uuid' => '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);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\Teamer;
|
||||
|
||||
use App\Entity\Application;
|
||||
use App\Entity\Assignment;
|
||||
use App\Entity\User;
|
||||
use App\Form\TeamerApplicationType;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
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;
|
||||
|
||||
class ApplicationController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly LoggerInterface $logger
|
||||
) {
|
||||
}
|
||||
|
||||
#[Route('/teamer/application/{uuid}', name: 'app_teamer_application')]
|
||||
#[IsGranted('ROLE_TEAMER')]
|
||||
public function index(Assignment $assignment, Request $request): Response
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = $this->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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\Teamer;
|
||||
|
||||
use App\Entity\Assignment;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
|
||||
class AssignmentController extends AbstractController
|
||||
{
|
||||
#[Route('/teamer/assignment/{uuid}', name: 'app_teamer_assignment')]
|
||||
#[IsGranted('ROLE_TEAMER')]
|
||||
public function index(Assignment $assignment): Response
|
||||
{
|
||||
return $this->render('teamer/assignment.html.twig', [
|
||||
'assignment' => $assignment,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\Traits;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
trait ReturnUrlTrait
|
||||
{
|
||||
public function getReturnUrl(Request $request, string $defaultRoute, array $parameters = []): string
|
||||
{
|
||||
$defaultUrl = $this->generateUrl($defaultRoute, $parameters);
|
||||
|
||||
return rawurldecode($request->query->get('r', $defaultUrl));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
+25
-11
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)',
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
use App\Entity\Application;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormEvent;
|
||||
use Symfony\Component\Form\FormEvents;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Validator\Constraints\IsTrue;
|
||||
|
||||
class TeamerApplicationType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder->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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Model;
|
||||
|
||||
class BenefitsDto
|
||||
{
|
||||
public static array $defaults = [
|
||||
'anreise',
|
||||
'unterkunft',
|
||||
'verpflegung',
|
||||
'skipass',
|
||||
];
|
||||
|
||||
public static array $values = [
|
||||
'anreise' => 'Anreise im E&P Reisebus',
|
||||
'unterkunft' => 'Unterkunft',
|
||||
'verpflegung' => 'Verpflegung',
|
||||
'skipass' => 'Skipass',
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace App\Service\Teamer;
|
||||
|
||||
use App\Entity\Availability;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class AvailabilityProcessor
|
||||
{
|
||||
/**
|
||||
* Groups provided unsorted availabilities by year and month
|
||||
* [
|
||||
* '2023' => [
|
||||
* '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;
|
||||
}
|
||||
}
|
||||
@@ -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']]),
|
||||
];
|
||||
}
|
||||
|
||||
+30
-20
@@ -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 = '<ul class="'.$class.'">';
|
||||
foreach ($items as $item) {
|
||||
$list .= '<li>'.$item.'</li>';
|
||||
}
|
||||
$list .= '<ul>';
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
public function isCurrentRoute(string $route): bool
|
||||
{
|
||||
$requestedRoute = $this->requestStack->getMainRequest()->attributes->get('_route');
|
||||
@@ -115,6 +114,17 @@ class AppRuntime implements RuntimeExtensionInterface
|
||||
return $requestedRoute === $route;
|
||||
}
|
||||
|
||||
public function getEncodedReturnUrl(): string
|
||||
{
|
||||
$masterRequest = $this->requestStack->getMainRequest();
|
||||
|
||||
if (null === $masterRequest) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return rawurlencode($masterRequest->getRequestUri());
|
||||
}
|
||||
|
||||
public function renderQaAttribute(string $label, string $value = null): string
|
||||
{
|
||||
if ('test' !== $this->environment) {
|
||||
|
||||
Reference in New Issue
Block a user