feat: implement screendesign

This commit is contained in:
Björn Fromme
2026-03-16 11:59:12 +01:00
parent 39e6cc4d68
commit 4a2d1f4a02
84 changed files with 2807 additions and 2603 deletions
+5 -4
View File
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Form\Model;
use App\BusProNet\Model\BankAccount;
use Symfony\Component\Validator\Constraints as Assert;
/**
@@ -11,11 +12,11 @@ use Symfony\Component\Validator\Constraints as Assert;
*/
class BankAccountDto
{
#[Assert\NotBlank(message: 'Bitte geben Sie Ihre IBAN ein.')]
#[Assert\NotBlank(message: 'Bitte gib deine IBAN ein.')]
#[Assert\Iban(message: 'Die eingegebene IBAN ist ungültig.')]
public ?string $iban = null;
#[Assert\NotBlank(message: 'Bitte geben Sie den Kontoinhaber ein.')]
#[Assert\NotBlank(message: 'Bitte gib den Kontoinhaber ein.')]
#[Assert\Length(
min: 2,
max: 70,
@@ -30,10 +31,10 @@ class BankAccountDto
)]
public ?string $bankName = null;
#[Assert\IsTrue(message: 'Bitte akzeptieren Sie das SEPA-Mandat.')]
#[Assert\IsTrue(message: 'Bitte akzeptiere das SEPA-Mandat.')]
public bool $sepaMandateAccepted = false;
public static function fromBankAccount(\App\BusProNet\Model\BankAccount $bankAccount): static
public static function fromBankAccount(BankAccount $bankAccount): static
{
$instance = new static();
$instance->iban = $bankAccount->iban;
+5 -5
View File
@@ -41,7 +41,7 @@ class BookingDto
#[Assert\Choice(
choices: [Constants::PAYMENT_METHOD_TRANSFER, Constants::PAYMENT_METHOD_DEBIT],
message: 'Bitte wählen Sie eine gültige Zahlungsart.'
message: 'Bitte wähle eine gültige Zahlungsart.'
)]
public ?string $paymentMethod = Constants::PAYMENT_METHOD_TRANSFER;
@@ -255,7 +255,7 @@ class BookingDto
}
if (null === $this->bankAccount) {
$context->buildViolation('Bitte geben Sie Ihre Bankverbindung an.')
$context->buildViolation('Bitte gib deine Bankverbindung an.')
->atPath('bankAccount')
->addViolation();
@@ -263,19 +263,19 @@ class BookingDto
}
if (null === $this->bankAccount->iban || '' === trim($this->bankAccount->iban)) {
$context->buildViolation('Bitte geben Sie Ihre IBAN ein.')
$context->buildViolation('Bitte gib deine IBAN ein.')
->atPath('bankAccount.iban')
->addViolation();
}
if (null === $this->bankAccount->accountHolder || '' === trim($this->bankAccount->accountHolder)) {
$context->buildViolation('Bitte geben Sie den Kontoinhaber ein.')
$context->buildViolation('Bitte gib den Kontoinhaber ein.')
->atPath('bankAccount.accountHolder')
->addViolation();
}
if (false === $this->bankAccount->sepaMandateAccepted) {
$context->buildViolation('Bitte akzeptieren Sie das SEPA-Mandat.')
$context->buildViolation('Bitte akzeptiere das SEPA-Mandat.')
->atPath('bankAccount.sepaMandateAccepted')
->addViolation();
}
+4 -2
View File
@@ -15,7 +15,8 @@ class BookingSummaryDto
/**
* @param array<int, RoomSelectionDto> $selectedRooms Selected room DTOs from booking
* @param int $participantCount Total participant count from room capacity
* @param string $totalPrice Formatted total price (e.g., "1.234,56 €")
* @param float $totalPrice Total price before voucher deductions
* @param float $payableAmount Amount after voucher deductions
* @param array<array{room: mixed, count: int}> $groupedSelectedRooms Rooms grouped by participant assignments
* @param array<int, int> $assignmentCounts Room ID to participant count mapping
* @param array $pricingData Detailed pricing breakdown
@@ -24,7 +25,8 @@ class BookingSummaryDto
public function __construct(
public readonly array $selectedRooms,
public readonly int $participantCount,
public readonly string $totalPrice,
public readonly float $totalPrice,
public readonly float $payableAmount,
public readonly array $groupedSelectedRooms,
public readonly array $assignmentCounts,
public readonly array $pricingData,
-1
View File
@@ -35,7 +35,6 @@ class PersonalDataType extends AbstractType
->add('email', EmailType::class, [
'label' => 'E-Mail',
'property_path' => 'communication.email',
'sanitize_html' => true,
])
->add('phone', TextType::class, [
'label' => 'Telefon',
+1 -1
View File
@@ -29,10 +29,10 @@ class RegistrationType extends AbstractType
])
->add('name', TextType::class, [
'label' => 'Nachname',
'sanitize_html' => true,
])
->add('email', EmailType::class, [
'label' => 'E-Mail',
'sanitize_html' => true,
])
;
}
+16 -8
View File
@@ -8,6 +8,8 @@ use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolver;
class RoomSelectType extends AbstractType
@@ -20,15 +22,8 @@ class RoomSelectType extends AbstractType
$data = $event->getData();
$form = $event->getForm();
// Build label with pricing
$label = 'Anzahl '.$data->roomLabel;
if (null !== $data->roomPrice) {
$formattedPrice = number_format((float) $data->roomPrice, 2, ',', '.');
$label .= sprintf(' (€%s pro Person)', $formattedPrice);
}
$form->add('quantity', StepSelectChoiceType::class, [
'label' => $label,
'label' => false,
'required' => true,
'min_value' => 0,
'max_value' => $data->maxQuantity,
@@ -37,6 +32,19 @@ class RoomSelectType extends AbstractType
});
}
public function buildView(FormView $view, FormInterface $form, array $options): void
{
$data = $form->getData();
$view->vars['label_room'] = $data->roomLabel;
$view->vars['label_price'] = null;
if (null !== $data->roomPrice) {
$formattedPrice = number_format((float) $data->roomPrice, 2, ',', '.');
$view->vars['label_price'] = sprintf(' %s € pro Person', $formattedPrice);
}
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
@@ -167,7 +167,7 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
$participantIndex
),
'choice_value' => 'id',
'choice_label' => fn (?Service $service) => $this->formatServiceLabelWithPrice($service),
'choice_label' => fn (?Service $service) => $service?->label,
'choice_attr' => function (?Service $service) use ($bookingDto, $participantIndex) {
if (null === $service) {
return [];
@@ -205,7 +205,7 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
$participantIndex
),
'choice_value' => 'id',
'choice_label' => fn (?Service $service) => $this->formatServiceLabelWithPrice($service),
'choice_label' => fn (?Service $service) => $service?->label,
'choice_attr' => function (?Service $service) use ($bookingDto, $participantIndex) {
if (null === $service) {
return [];
@@ -251,7 +251,7 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
$participantIndex
),
'choice_value' => 'id',
'choice_label' => fn (?Service $service) => $this->formatServiceLabelWithPrice($service),
'choice_label' => fn (?Service $service) => $service?->label,
'choice_attr' => function (?Service $service) use ($bookingDto, $participantIndex) {
if (null === $service) {
return [];
@@ -294,7 +294,7 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
$participantIndex
),
'choice_value' => 'id',
'choice_label' => fn (?Service $service) => $this->formatServiceLabelWithPrice($service),
'choice_label' => fn (?Service $service) => $service?->label,
'choice_attr' => function (?Service $service) use ($bookingDto, $participantIndex) {
if (null === $service) {
return [];
@@ -353,7 +353,7 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
$participantIndex
),
'choice_value' => 'id',
'choice_label' => fn (?Service $service) => $this->formatServiceLabelWithPrice($service),
'choice_label' => fn (?Service $service) => $service?->label,
'choice_attr' => function (?Service $service) use ($bookingDto, $participantIndex) {
if (null === $service) {
return [];
@@ -396,7 +396,7 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
$bookingDto,
$participantIndex
),
'choice_label' => fn (Service $service) => $this->formatTransportationServiceLabel($service),
'choice_label' => fn (Service $service) => $service?->label,
'choice_value' => 'id',
'expanded' => true,
'multiple' => false,
@@ -427,7 +427,7 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
$this->fieldOptionProviders['transportationInbound'] = fn (BookingDto $bookingDto, int $participantIndex, array $options = []) => [
'label' => 'Rückfahrt',
'choices' => $bookingDto->travel->getTransportationServicesByDirection(DirectionMapper::INBOUND_TRAVEL),
'choice_label' => fn (Service $service) => $this->formatTransportationServiceLabel($service),
'choice_label' => fn (Service $service) => $service?->label,
'choice_value' => 'id',
'expanded' => true,
'multiple' => false,
@@ -653,41 +653,6 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
return sprintf('%s (€%s)', $service->label, number_format($service->price, 2, ',', '.'));
}
/**
* Format transportation service labels with type indicator and pricing.
*
* Creates user-friendly labels for transportation services that include:
* - Transportation type icon (🚌 for bus, 🚗 for car)
* - Service name
* - Pricing (with discount indication for negative prices)
* - Availability warning for limited services
*
* @param Service $service The transportation service to format
*
* @return string The formatted transportation service label
*/
private function formatTransportationServiceLabel(Service $service): string
{
$label = $service->label;
if (null === $service->price || 0.0 === $service->price) {
return $service->label;
}
if ($service->price > 0) {
$label .= sprintf(' (€%s)', number_format($service->price, 2, ',', '.'));
} else {
$label .= sprintf(' (-%s€ Rabatt)', number_format(abs($service->price), 2, ',', '.'));
}
// Add availability warning if limited
if (null !== $service->available && $service->available <= 5) {
$label .= sprintf(' (nur %d verfügbar)', $service->available);
}
return $label;
}
private function formatPickupLabelWithPrice(?Pickup $pickup): string
{
if (null === $pickup) {
@@ -935,11 +900,6 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
*/
private function filterTransportationChoices(array $services, BookingDto $bookingDto, int $participantIndex): array
{
// Only apply filtering in create mode
if (BookingDto::MODE_CREATE !== $bookingDto->getMode()) {
return $services;
}
// Separate PKW/CAR from other services (BUS, etc.)
$pkwServices = [];
$otherServices = [];