WIP: Implement frontend
This commit is contained in:
@@ -4,6 +4,7 @@ namespace App\BusProNet;
|
||||
|
||||
use App\BusProNet\Model\BaseResponse;
|
||||
use App\Entity\User;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Serializer\SerializerInterface;
|
||||
use Symfony\Contracts\Cache\CacheInterface;
|
||||
@@ -19,6 +20,7 @@ class ApiClient
|
||||
private readonly SerializerInterface $serializer,
|
||||
private readonly ResponseParser $responseParser,
|
||||
private readonly CacheInterface $cache,
|
||||
private readonly LoggerInterface $logger,
|
||||
array $options
|
||||
) {
|
||||
$this->config = $this->resolveOptions($options);
|
||||
@@ -57,6 +59,7 @@ class ApiClient
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
|
||||
$this->logger->error('API error', ['error' => $e->getMessage()]);
|
||||
throw new ApiClientException($e->getMessage());
|
||||
}
|
||||
|
||||
@@ -96,6 +99,7 @@ class ApiClient
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
|
||||
$this->logger->error('API error', ['error' => $e->getMessage()]);
|
||||
throw new ApiClientException($e->getMessage());
|
||||
}
|
||||
|
||||
@@ -128,6 +132,7 @@ class ApiClient
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
|
||||
$this->logger->error('API error', ['error' => $e->getMessage()]);
|
||||
throw new ApiClientException($e->getMessage());
|
||||
}
|
||||
|
||||
@@ -164,6 +169,7 @@ class ApiClient
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
|
||||
$this->logger->error('API error', ['error' => $e->getMessage()]);
|
||||
throw new ApiClientException($e->getMessage());
|
||||
}
|
||||
|
||||
@@ -197,6 +203,7 @@ class ApiClient
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
|
||||
$this->logger->error('API error', ['error' => $e->getMessage()]);
|
||||
throw new ApiClientException($e->getMessage());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use App\BusProNet\ApiClient;
|
||||
use App\BusProNet\ApiClientException;
|
||||
use App\Entity\Teamer;
|
||||
use App\Entity\User;
|
||||
use App\Form\ProfileType;
|
||||
use App\Form\TeamerProfileType;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
@@ -36,7 +36,7 @@ class ProfileController extends AbstractController
|
||||
$this->entityManager->persist($teamer);
|
||||
}
|
||||
|
||||
$form = $this->createForm(ProfileType::class, $teamer);
|
||||
$form = $this->createForm(TeamerProfileType::class, $teamer);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
@@ -47,6 +47,8 @@ class ProfileController extends AbstractController
|
||||
} catch (ApiClientException $e) {
|
||||
}
|
||||
|
||||
$this->addFlash('success', 'Deine Daten wurden aktualisiert');
|
||||
|
||||
return $this->redirectToRoute('app_teamer_profile');
|
||||
}
|
||||
|
||||
|
||||
@@ -11,15 +11,15 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||
class Address
|
||||
{
|
||||
#[ORM\Column(type: 'string', nullable: true)]
|
||||
#[Assert\NotBlank(message: 'required_input')]
|
||||
#[Assert\NotBlank(message: 'Bitte angeben')]
|
||||
protected ?string $street = null;
|
||||
|
||||
#[ORM\Column(type: 'string', nullable: true)]
|
||||
#[Assert\NotBlank(message: 'required_input')]
|
||||
#[Assert\NotBlank(message: 'Bitte angeben')]
|
||||
protected ?string $postCode = null;
|
||||
|
||||
#[ORM\Column(type: 'string', nullable: true)]
|
||||
#[Assert\NotBlank(message: 'required_input')]
|
||||
#[Assert\NotBlank(message: 'Bitte angeben')]
|
||||
protected ?string $city = null;
|
||||
|
||||
#[ORM\Column(type: 'string', nullable: true)]
|
||||
|
||||
@@ -3,20 +3,26 @@
|
||||
namespace App\Entity\Embeddable;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
#[ORM\Embeddable]
|
||||
class BankAccount
|
||||
{
|
||||
#[ORM\Column(nullable: true)]
|
||||
#[Assert\NotBlank(message: 'Bitte angeben')]
|
||||
#[Assert\Iban(message: 'Diese IBAN ist ungültig')]
|
||||
private ?string $iban = null;
|
||||
|
||||
#[ORM\Column(nullable: true)]
|
||||
#[Assert\NotBlank(message: 'Bitte angeben')]
|
||||
private ?string $bic = null;
|
||||
|
||||
#[ORM\Column(nullable: true)]
|
||||
#[Assert\NotBlank(message: 'Bitte angeben')]
|
||||
private ?string $bank = null;
|
||||
|
||||
#[ORM\Column(nullable: true)]
|
||||
#[Assert\NotBlank(message: 'Bitte angeben')]
|
||||
private ?string $holder = null;
|
||||
|
||||
public function getIban(bool $obfuscated = false): ?string
|
||||
|
||||
@@ -13,10 +13,12 @@ class Communication
|
||||
protected ?string $phone = null;
|
||||
|
||||
#[ORM\Column(type: 'string', nullable: true)]
|
||||
#[Assert\NotBlank(message: 'Bitte angeben')]
|
||||
protected ?string $mobile = null;
|
||||
|
||||
#[ORM\Column(type: 'string', nullable: true)]
|
||||
#[Assert\Email(mode: 'strict')]
|
||||
#[Assert\Email(mode: 'strict', message: 'Diese E-Mail-Adresse ist ungültig')]
|
||||
#[Assert\NotBlank(message: 'Bitte angeben')]
|
||||
protected ?string $email = null;
|
||||
|
||||
public function toPayload(): array
|
||||
|
||||
+15
-7
@@ -32,15 +32,19 @@ class Teamer implements TimestampableEntityInterface
|
||||
private string $uuid;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
#[Assert\NotBlank(message: 'Bitte angeben')]
|
||||
private ?string $firstName = null;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
#[Assert\NotBlank(message: 'Bitte angeben')]
|
||||
private ?string $lastName = null;
|
||||
|
||||
#[ORM\Column(length: 1)]
|
||||
#[Assert\NotBlank(message: 'Bitte angeben')]
|
||||
private ?string $gender = null;
|
||||
|
||||
#[ORM\Column(type: Types::DATE_IMMUTABLE)]
|
||||
#[Assert\NotNull(message: 'Bitte angeben')]
|
||||
private ?\DateTimeImmutable $dateOfBirth = null;
|
||||
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
@@ -50,6 +54,7 @@ class Teamer implements TimestampableEntityInterface
|
||||
private ?string $salutation = null;
|
||||
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
#[Assert\NotBlank(message: 'Bitte angeben')]
|
||||
private ?string $nationality = null;
|
||||
|
||||
#[ORM\Embedded(class: Address::class)]
|
||||
@@ -65,13 +70,15 @@ class Teamer implements TimestampableEntityInterface
|
||||
private ?BankAccount $bankAccount = null;
|
||||
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
#[Assert\NotBlank(message: 'Bitte angeben')]
|
||||
private ?string $taxId = null;
|
||||
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
#[Assert\NotBlank(message: 'Bitte angeben')]
|
||||
private ?string $healthInsuranceCompany = null;
|
||||
|
||||
#[ORM\Column(length: 32)]
|
||||
private ?string $status = null;
|
||||
private ?string $status;
|
||||
|
||||
#[ORM\Column(type: Types::TEXT, nullable: true)]
|
||||
private ?string $remarks = null;
|
||||
@@ -121,7 +128,7 @@ class Teamer implements TimestampableEntityInterface
|
||||
{
|
||||
// Transform gender value
|
||||
$gender = strtoupper($this->getGender());
|
||||
$gender = 'F' ? 'W' : $gender;
|
||||
$gender = 'F' === $gender ? 'W' : $gender;
|
||||
|
||||
// Ensure date of birth is populated
|
||||
if (null === $dob = $this->getDateOfBirth()) {
|
||||
@@ -130,6 +137,7 @@ class Teamer implements TimestampableEntityInterface
|
||||
|
||||
return [
|
||||
'geburtsdatum' => $dob->format('d.m.Y'),
|
||||
'anrede' => $this->getSalutation(),
|
||||
'geschlecht' => $gender,
|
||||
'titel' => $this->getAcademicTitle(),
|
||||
'vorname' => $this->getFirstName(),
|
||||
@@ -178,7 +186,7 @@ class Teamer implements TimestampableEntityInterface
|
||||
return $this->firstName;
|
||||
}
|
||||
|
||||
public function setFirstName(string $firstName): static
|
||||
public function setFirstName(?string $firstName): static
|
||||
{
|
||||
$this->firstName = $firstName;
|
||||
|
||||
@@ -190,7 +198,7 @@ class Teamer implements TimestampableEntityInterface
|
||||
return $this->lastName;
|
||||
}
|
||||
|
||||
public function setLastName(string $lastName): static
|
||||
public function setLastName(?string $lastName): static
|
||||
{
|
||||
$this->lastName = $lastName;
|
||||
|
||||
@@ -202,7 +210,7 @@ class Teamer implements TimestampableEntityInterface
|
||||
return $this->gender;
|
||||
}
|
||||
|
||||
public function setGender(string $gender): static
|
||||
public function setGender(?string $gender): static
|
||||
{
|
||||
$this->gender = $gender;
|
||||
|
||||
@@ -214,7 +222,7 @@ class Teamer implements TimestampableEntityInterface
|
||||
return $this->dateOfBirth;
|
||||
}
|
||||
|
||||
public function setDateOfBirth(\DateTimeImmutable $dateOfBirth): static
|
||||
public function setDateOfBirth(?\DateTimeImmutable $dateOfBirth): static
|
||||
{
|
||||
$this->dateOfBirth = $dateOfBirth;
|
||||
|
||||
@@ -250,7 +258,7 @@ class Teamer implements TimestampableEntityInterface
|
||||
return $this->nationality;
|
||||
}
|
||||
|
||||
public function setNationality(string $nationality): static
|
||||
public function setNationality(?string $nationality): static
|
||||
{
|
||||
$this->nationality = $nationality;
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
use App\Entity\Embeddable\Address;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class AddressType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('street', TextType::class, [
|
||||
'label' => 'Straße, Hausnummer',
|
||||
])
|
||||
->add('postCode', TextType::class, [
|
||||
'label' => 'PLZ',
|
||||
])
|
||||
->add('city', TextType::class, [
|
||||
'label' => 'Ort',
|
||||
])
|
||||
->add('country', BpnCountryType::class, [
|
||||
'label' => 'Land',
|
||||
'property' => 'country',
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => Address::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
use App\Entity\Embeddable\BankAccount;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class BankAccountType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('iban', TextType::class, [
|
||||
'label' => 'IBAN',
|
||||
])
|
||||
->add('bic', TextType::class, [
|
||||
'label' => 'BIC',
|
||||
])
|
||||
->add('bank', TextType::class, [
|
||||
'label' => 'Name der Bank',
|
||||
])
|
||||
->add('holder', TextType::class, [
|
||||
'label' => 'Kontoinhaber',
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => BankAccount::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ class BpnCountryChoiceLoader implements ChoiceLoaderInterface
|
||||
|
||||
foreach ($countries as $country) {
|
||||
$key = 'nationality' === $this->property ? $country->getNationality() : $country->getName();
|
||||
$choices[$key] = $country->getId();
|
||||
$choices[$key] = $country->getToken();
|
||||
}
|
||||
|
||||
return new ArrayChoiceList($choices);
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
use App\Entity\Embeddable\Communication;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\EmailType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class CommunicationType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('email', EmailType::class, [
|
||||
'label' => 'E-Mail',
|
||||
])
|
||||
->add('mobile', TextType::class, [
|
||||
'label' => 'Telefon (mobil)',
|
||||
])
|
||||
->add('phone', TextType::class, [
|
||||
'label' => 'Telefon (privat)',
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => Communication::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Form\Extension;
|
||||
|
||||
use Symfony\Component\Form\AbstractTypeExtension;
|
||||
use Symfony\Component\Form\Extension\Core\Type\FormType;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Form\FormView;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class AjaxSubmitExtension extends AbstractTypeExtension
|
||||
{
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'ajax_submit' => false,
|
||||
'ajax_action' => 'ajax-modal#submitForm',
|
||||
]);
|
||||
}
|
||||
|
||||
public function buildView(FormView $view, FormInterface $form, array $options): void
|
||||
{
|
||||
if (true === $options['ajax_submit']) {
|
||||
$view->vars['attr'] = array_merge($view->vars['attr'], ['data-action' => $options['ajax_action']]);
|
||||
}
|
||||
}
|
||||
|
||||
public static function getExtendedTypes(): iterable
|
||||
{
|
||||
return [FormType::class];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Form\Extension;
|
||||
|
||||
use Symfony\Component\Form\AbstractTypeExtension;
|
||||
use Symfony\Component\Form\Extension\Core\Type\FormType;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Form\FormView;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class NoValidateExtension extends AbstractTypeExtension
|
||||
{
|
||||
public function buildView(FormView $view, FormInterface $form, array $options): void
|
||||
{
|
||||
if (false === $options['clientside_validation'] && $form->isRoot()) {
|
||||
$view->vars['attr'] = array_merge($view->vars['attr'], ['novalidate' => 'novalidate']);
|
||||
}
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefault('clientside_validation', false);
|
||||
}
|
||||
|
||||
public static function getExtendedTypes(): iterable
|
||||
{
|
||||
return [FormType::class];
|
||||
}
|
||||
}
|
||||
@@ -6,11 +6,12 @@ use App\Entity\Teamer;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\BirthdayType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class ProfileType extends AbstractType
|
||||
class TeamerProfileType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
@@ -23,6 +24,14 @@ class ProfileType extends AbstractType
|
||||
'divers' => 'D',
|
||||
],
|
||||
])
|
||||
->add('salutation', ChoiceType::class, [
|
||||
'label' => 'Anrede',
|
||||
'choices' => [
|
||||
'Frau' => 'Frau',
|
||||
'Herr' => 'Herr',
|
||||
'divers' => 'divers',
|
||||
],
|
||||
])
|
||||
->add('firstName', TextType::class, [
|
||||
'label' => 'Vorname',
|
||||
])
|
||||
@@ -41,6 +50,30 @@ class ProfileType extends AbstractType
|
||||
'label' => 'Nationalität',
|
||||
'property' => 'nationality',
|
||||
])
|
||||
->add('taxId', TextType::class, [
|
||||
'label' => 'Steuer-ID',
|
||||
'required' => false,
|
||||
])
|
||||
->add('healthInsuranceCompany', TextType::class, [
|
||||
'label' => 'Krankenversicherung',
|
||||
'required' => false,
|
||||
])
|
||||
->add('remarks', TextareaType::class, [
|
||||
'label' => 'Wünsche/Anmerkungen',
|
||||
'required' => false,
|
||||
'attr' => [
|
||||
'rows' => 3,
|
||||
],
|
||||
])
|
||||
->add('address', AddressType::class, [
|
||||
'label' => 'false',
|
||||
])
|
||||
->add('communication', CommunicationType::class, [
|
||||
'label' => false,
|
||||
])
|
||||
->add('bankAccount', BankAccountType::class, [
|
||||
'label' => false,
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace App\Menu;
|
||||
|
||||
use App\Entity\User;
|
||||
use Knp\Menu\FactoryInterface;
|
||||
use Knp\Menu\ItemInterface;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class MenuBuilder
|
||||
{
|
||||
public function __construct(
|
||||
protected FactoryInterface $factory,
|
||||
protected Security $security,
|
||||
protected RequestStack $requestStack,
|
||||
protected TranslatorInterface $translator
|
||||
) {
|
||||
}
|
||||
|
||||
private function assertMobile(array $options): bool
|
||||
{
|
||||
return isset($options['mobile']) && true === (bool) $options['mobile'];
|
||||
}
|
||||
|
||||
private function createRootElement(bool $isMobile): ItemInterface
|
||||
{
|
||||
return $this
|
||||
->factory->createItem('root')
|
||||
->setChildrenAttribute('class', 'menu'.($isMobile ? ' menu--mobile' : ''))
|
||||
;
|
||||
}
|
||||
|
||||
private function getDefaultRouteParameters(string $parameter = 'id', string $default = '0'): array
|
||||
{
|
||||
return [$parameter => $this->requestStack->getMainRequest()->get($parameter, $default)];
|
||||
}
|
||||
|
||||
public function createMainMenu(array $options): ItemInterface
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = $this->security->getUser();
|
||||
$userLabel = $user->getUserIdentifier();
|
||||
|
||||
$isMobile = $this->assertMobile($options);
|
||||
$menu = $this->createRootElement($isMobile);
|
||||
|
||||
if ($this->security->isGranted('ROLE_TEAMER')) {
|
||||
$menu
|
||||
->addChild('Profil', ['route' => 'app_teamer_profile'])
|
||||
->setLinkAttribute('title', 'Benutzer: '.$userLabel)
|
||||
->setExtra('icon', 'user')
|
||||
->setExtra('icon_only', false === $isMobile)
|
||||
;
|
||||
}
|
||||
|
||||
$token = $this->security->getToken();
|
||||
|
||||
if ($token instanceof SwitchUserToken) {
|
||||
/** @var User $originalUser */
|
||||
$originalUser = $token->getOriginalToken()->getUser();
|
||||
$menu
|
||||
->addChild('Logout', [
|
||||
'route' => $originalUser->getDefaultRoute(),
|
||||
'routeParameters' => ['_switch_user' => '_exit'],
|
||||
])
|
||||
->setLinkAttributes([
|
||||
'title' => 'zurück',
|
||||
'class' => 'icon-link',
|
||||
])
|
||||
->setExtra('icon', 'logout')
|
||||
->setExtra('icon_only', false === $isMobile)
|
||||
;
|
||||
} else {
|
||||
$menu
|
||||
->addChild('Logout', ['route' => 'app_security_logout'])
|
||||
->setLinkAttributes([
|
||||
'title' => 'Logout',
|
||||
'class' => 'icon-link',
|
||||
])
|
||||
->setExtra('icon', 'logout')
|
||||
->setExtra('icon_only', false === $isMobile)
|
||||
;
|
||||
}
|
||||
|
||||
return $menu;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Twig;
|
||||
|
||||
use Twig\Extension\AbstractExtension;
|
||||
use Twig\TwigFilter;
|
||||
use Twig\TwigFunction;
|
||||
|
||||
class AppExtension extends AbstractExtension
|
||||
{
|
||||
public function getFilters(): array
|
||||
{
|
||||
return [
|
||||
new TwigFilter('file_size', [AppRuntime::class, 'formatBytes']),
|
||||
new TwigFilter('file_icon', [AppRuntime::class, 'fileIconFilter'], ['is_safe' => ['html']]),
|
||||
new TwigFilter('date_diff', [AppRuntime::class, 'dateDiffForHumans']),
|
||||
];
|
||||
}
|
||||
|
||||
public function getFunctions(): array
|
||||
{
|
||||
return [
|
||||
new TwigFunction('icon', [AppRuntime::class, 'renderIcon'], ['needs_environment' => true, 'is_safe' => ['html']]),
|
||||
new TwigFunction('is_current_route', [AppRuntime::class, 'isCurrentRoute']),
|
||||
new TwigFunction('qa_attribute', [AppRuntime::class, 'renderQaAttribute'], ['is_safe' => ['html']]),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace App\Twig;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Twig\Environment;
|
||||
use Twig\Extension\RuntimeExtensionInterface;
|
||||
|
||||
class AppRuntime implements RuntimeExtensionInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly RequestStack $requestStack,
|
||||
private readonly string $environment
|
||||
) {
|
||||
}
|
||||
|
||||
public function fileIconFilter(Environment $environment, string $mimeType, ?string $classes = 'w-4 h-4'): string
|
||||
{
|
||||
$icon = match ($mimeType) {
|
||||
'application/pdf' => 'pdf',
|
||||
'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'excel',
|
||||
'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'word',
|
||||
default => 'download',
|
||||
};
|
||||
|
||||
return $this->renderIcon($environment, $icon, $classes);
|
||||
}
|
||||
|
||||
public function formatBytes(int $bytes, ?int $precision = 2): string
|
||||
{
|
||||
$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];
|
||||
}
|
||||
|
||||
public function renderIcon(Environment $environment, string $icon, string $classes = 'w-6 h-6'): string
|
||||
{
|
||||
return $environment->render('_partials/_icon.html.twig', [
|
||||
'icon' => $icon,
|
||||
'class' => $classes,
|
||||
]);
|
||||
}
|
||||
|
||||
public function isCurrentRoute(string $route): bool
|
||||
{
|
||||
$requestedRoute = $this->requestStack->getMainRequest()->attributes->get('_route');
|
||||
|
||||
return $requestedRoute === $route;
|
||||
}
|
||||
|
||||
public function renderQaAttribute(string $label, string $value = null): string
|
||||
{
|
||||
if ('test' !== $this->environment) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (null === $value) {
|
||||
return sprintf(' data-qa-%s', strtolower($label));
|
||||
}
|
||||
|
||||
return sprintf(' data-qa-%s="%s"', strtolower($label), $value);
|
||||
}
|
||||
|
||||
public function dateDiffForHumans(\DateTimeInterface $dateTime, \DateTimeInterface $other = null): string
|
||||
{
|
||||
$oldLocale = Carbon::getLocale();
|
||||
$locale = $this->requestStack->getMainRequest()->getLocale();
|
||||
Carbon::setLocale($locale);
|
||||
|
||||
$result = Carbon::instance($dateTime)->diffForHumans($other);
|
||||
|
||||
Carbon::setLocale($oldLocale);
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user