feat: improved handling of fields to be rendered as static text
This commit is contained in:
@@ -31,9 +31,15 @@ abstract class AbstractFieldStateProvider implements FieldStateProviderInterface
|
|||||||
/**
|
/**
|
||||||
* Determines whether a field should be included in the form at all.
|
* Determines whether a field should be included in the form at all.
|
||||||
*
|
*
|
||||||
* Fields with 'hidden' state conditions should not be added to the form
|
* Fields with 'hidden' or 'static_text' state conditions should not be added to the form structure.
|
||||||
* rather than being hidden with CSS. This method evaluates the hidden
|
* Both states exclude fields from the form to prevent form_rest() from rendering them.
|
||||||
* condition independently to allow early field exclusion.
|
*
|
||||||
|
* The distinction between 'hidden' and 'static_text':
|
||||||
|
* - 'hidden': Field completely excluded from form (not rendered at all)
|
||||||
|
* - 'static_text': Field excluded from form but template renders the value as static text
|
||||||
|
*
|
||||||
|
* Templates must check shouldRenderAsStaticText() to determine if they should
|
||||||
|
* display the field's value as static text when the field is not in the form.
|
||||||
*
|
*
|
||||||
* @param string $fieldName The name of the field to evaluate
|
* @param string $fieldName The name of the field to evaluate
|
||||||
* @param BookingDto $bookingDto The current booking data for context (create or edit)
|
* @param BookingDto $bookingDto The current booking data for context (create or edit)
|
||||||
@@ -44,13 +50,57 @@ abstract class AbstractFieldStateProvider implements FieldStateProviderInterface
|
|||||||
*/
|
*/
|
||||||
public function shouldIncludeField(string $fieldName, BookingDto $bookingDto, int $participantIndex, array $formData = []): bool
|
public function shouldIncludeField(string $fieldName, BookingDto $bookingDto, int $participantIndex, array $formData = []): bool
|
||||||
{
|
{
|
||||||
if (false === isset($this->fieldStateConditions[$fieldName]['hidden'])) {
|
// Check if field has 'hidden' condition
|
||||||
return true; // No hidden condition means field should be included
|
if (isset($this->fieldStateConditions[$fieldName]['hidden'])) {
|
||||||
|
$hiddenCondition = $this->fieldStateConditions[$fieldName]['hidden'];
|
||||||
|
if ($hiddenCondition->evaluate($bookingDto, $participantIndex, $formData)) {
|
||||||
|
return false; // Field is hidden, exclude from form
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$hiddenCondition = $this->fieldStateConditions[$fieldName]['hidden'];
|
// Check if field has 'static_text' condition
|
||||||
|
if (isset($this->fieldStateConditions[$fieldName]['static_text'])) {
|
||||||
|
$staticTextCondition = $this->fieldStateConditions[$fieldName]['static_text'];
|
||||||
|
if ($staticTextCondition->evaluate($bookingDto, $participantIndex, $formData)) {
|
||||||
|
return false; // Field should render as static text, exclude from form
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return !$hiddenCondition->evaluate($bookingDto, $participantIndex, $formData);
|
return true; // No conditions met, include field in form
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether a field should be rendered as static text instead of a form input.
|
||||||
|
*
|
||||||
|
* Fields with 'static_text' state are included in the form structure but should be
|
||||||
|
* displayed as read-only static text by the template. This is used for:
|
||||||
|
* - Personal data of authenticated users (prevents creating duplicate BPN records)
|
||||||
|
* - Fields locked by BPN's mutability rules (aenderungmoeglich flag)
|
||||||
|
* - Any other scenario where data should be visible but not editable
|
||||||
|
*
|
||||||
|
* Example usage in templates:
|
||||||
|
* {% if is_static_text(form.firstName, bookingDto, participantIndex) %}
|
||||||
|
* {{ render_static_field(form.firstName, participant.firstName) }}
|
||||||
|
* {% else %}
|
||||||
|
* {{ form_row(form.firstName) }}
|
||||||
|
* {% endif %}
|
||||||
|
*
|
||||||
|
* @param string $fieldName The name of the field to evaluate
|
||||||
|
* @param BookingDto $bookingDto The current booking data for context (create or edit)
|
||||||
|
* @param int $participantIndex The index of the participant being evaluated
|
||||||
|
* @param array<string, mixed> $formData Current form data for condition evaluation
|
||||||
|
*
|
||||||
|
* @return bool True if the field should be rendered as static text, false if it should be a normal form input
|
||||||
|
*/
|
||||||
|
public function shouldRenderAsStaticText(string $fieldName, BookingDto $bookingDto, int $participantIndex, array $formData = []): bool
|
||||||
|
{
|
||||||
|
if (false === isset($this->fieldStateConditions[$fieldName]['static_text'])) {
|
||||||
|
return false; // No static_text condition means field should render normally
|
||||||
|
}
|
||||||
|
|
||||||
|
$staticTextCondition = $this->fieldStateConditions[$fieldName]['static_text'];
|
||||||
|
|
||||||
|
return $staticTextCondition->evaluate($bookingDto, $participantIndex, $formData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -58,10 +108,17 @@ abstract class AbstractFieldStateProvider implements FieldStateProviderInterface
|
|||||||
*
|
*
|
||||||
* Evaluates all configured conditions for a field and returns the appropriate
|
* Evaluates all configured conditions for a field and returns the appropriate
|
||||||
* state modifications. State conditions are organized by state type (readonly,
|
* state modifications. State conditions are organized by state type (readonly,
|
||||||
* disabled, etc.) and evaluated independently.
|
* disabled, required, etc.) and evaluated independently.
|
||||||
*
|
*
|
||||||
* Note: This method no longer handles 'hidden' state as fields should be
|
* Supported state types:
|
||||||
* excluded from the form entirely rather than hidden with CSS.
|
* - 'readonly': Adds readonly HTML attribute to the field
|
||||||
|
* - 'disabled': Disables the field completely
|
||||||
|
* - 'required': Makes the field mandatory
|
||||||
|
* - 'static_text': Field should be rendered as static text (checked separately via shouldRenderAsStaticText())
|
||||||
|
* - 'hidden': Field excluded from form entirely (checked via shouldIncludeField())
|
||||||
|
*
|
||||||
|
* Note: 'static_text' and 'hidden' states are not returned by this method as they
|
||||||
|
* affect form structure/template rendering rather than Symfony form field options.
|
||||||
*
|
*
|
||||||
* @param string $fieldName The name of the field to evaluate
|
* @param string $fieldName The name of the field to evaluate
|
||||||
* @param BookingDto $bookingDto The current booking data for context (create or edit)
|
* @param BookingDto $bookingDto The current booking data for context (create or edit)
|
||||||
@@ -91,6 +148,8 @@ abstract class AbstractFieldStateProvider implements FieldStateProviderInterface
|
|||||||
case 'required':
|
case 'required':
|
||||||
$stateModifications['required'] = true;
|
$stateModifications['required'] = true;
|
||||||
break;
|
break;
|
||||||
|
// Note: 'static_text' and 'hidden' are intentionally not handled here
|
||||||
|
// They are checked via shouldRenderAsStaticText() and shouldIncludeField()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,11 +23,12 @@ use App\Service\ParticipantEligibilityService;
|
|||||||
/**
|
/**
|
||||||
* Field state provider for the booking create workflow.
|
* Field state provider for the booking create workflow.
|
||||||
*
|
*
|
||||||
* This service calculates dynamic field states (readonly, disabled, etc.)
|
* This service calculates dynamic field states (readonly, disabled, static_text, etc.)
|
||||||
* for participant fields in the create flow. It extends the common field
|
* for participant fields in the create flow. It extends the common field
|
||||||
* state functionality provided by AbstractFieldStateProvider.
|
* state functionality provided by AbstractFieldStateProvider.
|
||||||
*
|
*
|
||||||
* Current field state conditions:
|
* Current field state conditions:
|
||||||
|
* - Personal data fields render as static text for authenticated users (prevents duplicate BPN records)
|
||||||
* - Body dimension fields are hidden unless rental services are selected
|
* - Body dimension fields are hidden unless rental services are selected
|
||||||
* - Age-dependent service fields are hidden until birth date is provided
|
* - Age-dependent service fields are hidden until birth date is provided
|
||||||
* - Transportation pickup fields are hidden by default, shown only when transportation type is BUS
|
* - Transportation pickup fields are hidden by default, shown only when transportation type is BUS
|
||||||
@@ -75,58 +76,58 @@ class CreateFieldStateProvider extends AbstractFieldStateProvider
|
|||||||
$skiPassCondition = new SkiPassSelectionCondition();
|
$skiPassCondition = new SkiPassSelectionCondition();
|
||||||
|
|
||||||
// Authenticated user personal data protection
|
// Authenticated user personal data protection
|
||||||
// Hide personal data fields for participants linked to BPN accounts (prevents duplicate records)
|
// Render personal data fields as static text for participants linked to BPN accounts (prevents duplicate records)
|
||||||
$authenticatedUserCondition = new AuthenticatedUserPersonalDataCondition();
|
$authenticatedUserCondition = new AuthenticatedUserPersonalDataCondition();
|
||||||
|
|
||||||
$this->fieldStateConditions['firstName'] = [
|
$this->fieldStateConditions['firstName'] = [
|
||||||
'hidden' => $authenticatedUserCondition,
|
'static_text' => $authenticatedUserCondition,
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->fieldStateConditions['lastName'] = [
|
$this->fieldStateConditions['lastName'] = [
|
||||||
'hidden' => $authenticatedUserCondition,
|
'static_text' => $authenticatedUserCondition,
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->fieldStateConditions['gender'] = [
|
$this->fieldStateConditions['gender'] = [
|
||||||
'hidden' => $authenticatedUserCondition,
|
'static_text' => $authenticatedUserCondition,
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->fieldStateConditions['nationality'] = [
|
$this->fieldStateConditions['nationality'] = [
|
||||||
'hidden' => $authenticatedUserCondition,
|
'static_text' => $authenticatedUserCondition,
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->fieldStateConditions['dateOfBirth'] = [
|
$this->fieldStateConditions['dateOfBirth'] = [
|
||||||
'hidden' => $authenticatedUserCondition,
|
'static_text' => $authenticatedUserCondition,
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->fieldStateConditions['email'] = [
|
$this->fieldStateConditions['email'] = [
|
||||||
'hidden' => $authenticatedUserCondition,
|
'static_text' => $authenticatedUserCondition,
|
||||||
];
|
];
|
||||||
|
|
||||||
// Mobile field: hidden for authenticated users, required for guest applicants
|
// Mobile field: static text for authenticated users, required for guest applicants
|
||||||
$this->fieldStateConditions['mobile'] = [
|
$this->fieldStateConditions['mobile'] = [
|
||||||
'hidden' => $authenticatedUserCondition,
|
'static_text' => $authenticatedUserCondition,
|
||||||
'required' => new ApplicantCondition(),
|
'required' => new ApplicantCondition(),
|
||||||
];
|
];
|
||||||
|
|
||||||
// Address subfields - must be hidden to prevent creating duplicate BPN records
|
// Address subfields - must render as static text to prevent creating duplicate BPN records
|
||||||
$this->fieldStateConditions['address.street'] = [
|
$this->fieldStateConditions['address.street'] = [
|
||||||
'hidden' => $authenticatedUserCondition,
|
'static_text' => $authenticatedUserCondition,
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->fieldStateConditions['address.postCode'] = [
|
$this->fieldStateConditions['address.postCode'] = [
|
||||||
'hidden' => $authenticatedUserCondition,
|
'static_text' => $authenticatedUserCondition,
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->fieldStateConditions['address.city'] = [
|
$this->fieldStateConditions['address.city'] = [
|
||||||
'hidden' => $authenticatedUserCondition,
|
'static_text' => $authenticatedUserCondition,
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->fieldStateConditions['address.country'] = [
|
$this->fieldStateConditions['address.country'] = [
|
||||||
'hidden' => $authenticatedUserCondition,
|
'static_text' => $authenticatedUserCondition,
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->fieldStateConditions['address.district'] = [
|
$this->fieldStateConditions['address.district'] = [
|
||||||
'hidden' => $authenticatedUserCondition,
|
'static_text' => $authenticatedUserCondition,
|
||||||
];
|
];
|
||||||
|
|
||||||
// Note: Body dimensions (height, weight, shoeSize) are NOT hidden for authenticated users
|
// Note: Body dimensions (height, weight, shoeSize) are NOT hidden for authenticated users
|
||||||
@@ -255,7 +256,9 @@ class CreateFieldStateProvider extends AbstractFieldStateProvider
|
|||||||
];
|
];
|
||||||
|
|
||||||
// Make address field required for applicant (participant index 0)
|
// Make address field required for applicant (participant index 0)
|
||||||
|
// Also render as static text for authenticated users to prevent duplicate BPN records
|
||||||
$this->fieldStateConditions['address'] = [
|
$this->fieldStateConditions['address'] = [
|
||||||
|
'static_text' => $authenticatedUserCondition,
|
||||||
'required' => new ApplicantCondition(),
|
'required' => new ApplicantCondition(),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -45,11 +45,11 @@ class EditFieldStateProvider extends AbstractFieldStateProvider
|
|||||||
$pickupsMutabilityCondition = new PickupsMutabilityCondition();
|
$pickupsMutabilityCondition = new PickupsMutabilityCondition();
|
||||||
|
|
||||||
// Authenticated user personal data protection
|
// Authenticated user personal data protection
|
||||||
// Hide personal data fields for participants linked to BPN accounts (prevents duplicate records)
|
// Render personal data fields as static text for participants linked to BPN accounts (prevents duplicate records)
|
||||||
$authenticatedUserCondition = new AuthenticatedUserPersonalDataCondition();
|
$authenticatedUserCondition = new AuthenticatedUserPersonalDataCondition();
|
||||||
|
|
||||||
// Make all personal data fields readonly if participant not mutable
|
// Make all personal data fields readonly if participant not mutable
|
||||||
// OR hidden if participant is linked to BPN account (authenticated user)
|
// OR static text if participant is linked to BPN account (authenticated user)
|
||||||
// Note: First participant is now treated as independent from applicant and can be edited
|
// Note: First participant is now treated as independent from applicant and can be edited
|
||||||
$personalDataFields = [
|
$personalDataFields = [
|
||||||
'firstName',
|
'firstName',
|
||||||
@@ -62,36 +62,36 @@ class EditFieldStateProvider extends AbstractFieldStateProvider
|
|||||||
];
|
];
|
||||||
foreach ($personalDataFields as $field) {
|
foreach ($personalDataFields as $field) {
|
||||||
$this->fieldStateConditions[$field] = [
|
$this->fieldStateConditions[$field] = [
|
||||||
'hidden' => $authenticatedUserCondition,
|
'static_text' => $authenticatedUserCondition,
|
||||||
'readonly' => CompositeCondition::not(new MutabilityCondition()),
|
'readonly' => CompositeCondition::not(new MutabilityCondition()),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Address fields - hidden for authenticated users, readonly if participant not mutable
|
// Address fields - static text for authenticated users, readonly if participant not mutable
|
||||||
$this->fieldStateConditions['address'] = [
|
$this->fieldStateConditions['address'] = [
|
||||||
'hidden' => $authenticatedUserCondition,
|
'static_text' => $authenticatedUserCondition,
|
||||||
'readonly' => CompositeCondition::not(new MutabilityCondition()),
|
'readonly' => CompositeCondition::not(new MutabilityCondition()),
|
||||||
];
|
];
|
||||||
|
|
||||||
// Address subfields - must be hidden to prevent creating duplicate BPN records
|
// Address subfields - must render as static text to prevent creating duplicate BPN records
|
||||||
$this->fieldStateConditions['address.street'] = [
|
$this->fieldStateConditions['address.street'] = [
|
||||||
'hidden' => $authenticatedUserCondition,
|
'static_text' => $authenticatedUserCondition,
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->fieldStateConditions['address.postCode'] = [
|
$this->fieldStateConditions['address.postCode'] = [
|
||||||
'hidden' => $authenticatedUserCondition,
|
'static_text' => $authenticatedUserCondition,
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->fieldStateConditions['address.city'] = [
|
$this->fieldStateConditions['address.city'] = [
|
||||||
'hidden' => $authenticatedUserCondition,
|
'static_text' => $authenticatedUserCondition,
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->fieldStateConditions['address.country'] = [
|
$this->fieldStateConditions['address.country'] = [
|
||||||
'hidden' => $authenticatedUserCondition,
|
'static_text' => $authenticatedUserCondition,
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->fieldStateConditions['address.district'] = [
|
$this->fieldStateConditions['address.district'] = [
|
||||||
'hidden' => $authenticatedUserCondition,
|
'static_text' => $authenticatedUserCondition,
|
||||||
];
|
];
|
||||||
|
|
||||||
// Conditional visibility for service fields (same as create flow)
|
// Conditional visibility for service fields (same as create flow)
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ class AppExtension extends AbstractExtension
|
|||||||
new TwigFunction('icon', [AppRuntime::class, 'renderIcon'], ['needs_environment' => true, 'is_safe' => ['html']]),
|
new TwigFunction('icon', [AppRuntime::class, 'renderIcon'], ['needs_environment' => true, 'is_safe' => ['html']]),
|
||||||
new TwigFunction('is_participant_eligible', [AppRuntime::class, 'isParticipantEligible']),
|
new TwigFunction('is_participant_eligible', [AppRuntime::class, 'isParticipantEligible']),
|
||||||
new TwigFunction('qa_attribute', [AppRuntime::class, 'renderQaAttribute'], ['is_safe' => ['html']]),
|
new TwigFunction('qa_attribute', [AppRuntime::class, 'renderQaAttribute'], ['is_safe' => ['html']]),
|
||||||
|
new TwigFunction('is_static_text', [AppRuntime::class, 'isStaticText']),
|
||||||
|
new TwigFunction('is_hidden', [AppRuntime::class, 'isHidden']),
|
||||||
|
new TwigFunction('gravatar_url', [AppRuntime::class, 'getGravatarUrl']),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,10 @@ namespace App\Twig;
|
|||||||
|
|
||||||
use App\BusProNet\DataProvider\CountryDataProvider;
|
use App\BusProNet\DataProvider\CountryDataProvider;
|
||||||
use App\Form\Model\BookingDto;
|
use App\Form\Model\BookingDto;
|
||||||
|
use App\Form\Service\CreateFieldStateProvider;
|
||||||
|
use App\Form\Service\EditFieldStateProvider;
|
||||||
use App\Service\ParticipantEligibilityService;
|
use App\Service\ParticipantEligibilityService;
|
||||||
|
use Symfony\Component\Form\FormView;
|
||||||
use Twig\Environment;
|
use Twig\Environment;
|
||||||
use Twig\Extension\RuntimeExtensionInterface;
|
use Twig\Extension\RuntimeExtensionInterface;
|
||||||
use Twig\Extra\Intl\IntlExtension;
|
use Twig\Extra\Intl\IntlExtension;
|
||||||
@@ -15,6 +18,8 @@ class AppRuntime implements RuntimeExtensionInterface
|
|||||||
private readonly IntlExtension $intlExtension,
|
private readonly IntlExtension $intlExtension,
|
||||||
private readonly CountryDataProvider $countryDataProvider,
|
private readonly CountryDataProvider $countryDataProvider,
|
||||||
private readonly ParticipantEligibilityService $participantEligibilityService,
|
private readonly ParticipantEligibilityService $participantEligibilityService,
|
||||||
|
private readonly CreateFieldStateProvider $createFieldStateProvider,
|
||||||
|
private readonly EditFieldStateProvider $editFieldStateProvider,
|
||||||
private readonly string $environment,
|
private readonly string $environment,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
@@ -123,4 +128,73 @@ class AppRuntime implements RuntimeExtensionInterface
|
|||||||
|
|
||||||
return sprintf(' data-qa-%s="%s"', strtolower($label), $value);
|
return sprintf(' data-qa-%s="%s"', strtolower($label), $value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a form field should be rendered as static text.
|
||||||
|
*
|
||||||
|
* Accepts either a FormView (when field is in form structure) or a string field name
|
||||||
|
* (when field is excluded from form but needs to be checked).
|
||||||
|
*
|
||||||
|
* @param FormView|string $field The FormView or field name
|
||||||
|
* @param BookingDto $bookingDto The booking context (create or edit mode)
|
||||||
|
* @param int $participantIndex The participant index
|
||||||
|
*
|
||||||
|
* @return bool True if field should render as static text
|
||||||
|
*/
|
||||||
|
public function isStaticText(FormView|string $field, BookingDto $bookingDto, int $participantIndex): bool
|
||||||
|
{
|
||||||
|
$fieldName = $field instanceof FormView ? $field->vars['name'] : $field;
|
||||||
|
$provider = $this->getProvider($bookingDto);
|
||||||
|
|
||||||
|
return $provider->shouldRenderAsStaticText($fieldName, $bookingDto, $participantIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a form field should be completely hidden (not rendered at all).
|
||||||
|
*
|
||||||
|
* Accepts either a FormView (when field is in form structure) or a string field name
|
||||||
|
* (when field is excluded from form but needs to be checked).
|
||||||
|
*
|
||||||
|
* @param FormView|string $field The FormView or field name
|
||||||
|
* @param BookingDto $bookingDto The booking context (create or edit mode)
|
||||||
|
* @param int $participantIndex The participant index
|
||||||
|
*
|
||||||
|
* @return bool True if field should be hidden (not rendered)
|
||||||
|
*/
|
||||||
|
public function isHidden(FormView|string $field, BookingDto $bookingDto, int $participantIndex): bool
|
||||||
|
{
|
||||||
|
$fieldName = $field instanceof FormView ? $field->vars['name'] : $field;
|
||||||
|
$provider = $this->getProvider($bookingDto);
|
||||||
|
|
||||||
|
return !$provider->shouldIncludeField($fieldName, $bookingDto, $participantIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a Gravatar URL for the given email address.
|
||||||
|
*
|
||||||
|
* @param string $email The email address
|
||||||
|
* @param int $size The size of the avatar in pixels (default: 80)
|
||||||
|
*
|
||||||
|
* @return string The Gravatar URL
|
||||||
|
*/
|
||||||
|
public function getGravatarUrl(string $email, int $size = 80): string
|
||||||
|
{
|
||||||
|
$hash = md5(strtolower(trim($email)));
|
||||||
|
|
||||||
|
return sprintf('https://www.gravatar.com/avatar/%s?s=%d&d=404', $hash, $size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the appropriate field state provider based on booking mode.
|
||||||
|
*
|
||||||
|
* @param BookingDto $bookingDto The booking context
|
||||||
|
*
|
||||||
|
* @return CreateFieldStateProvider|EditFieldStateProvider The appropriate provider
|
||||||
|
*/
|
||||||
|
private function getProvider(BookingDto $bookingDto): CreateFieldStateProvider|EditFieldStateProvider
|
||||||
|
{
|
||||||
|
return BookingDto::MODE_CREATE === $bookingDto->getMode()
|
||||||
|
? $this->createFieldStateProvider
|
||||||
|
: $this->editFieldStateProvider;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Twig;
|
|
||||||
|
|
||||||
use Twig\Extension\AbstractExtension;
|
|
||||||
use Twig\TwigFunction;
|
|
||||||
|
|
||||||
final class GravatarExtension extends AbstractExtension
|
|
||||||
{
|
|
||||||
public function getFunctions(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
new TwigFunction('gravatar_url', [$this, 'getGravatarUrl']),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a Gravatar URL for the given email address.
|
|
||||||
*/
|
|
||||||
public function getGravatarUrl(string $email, int $size = 80): string
|
|
||||||
{
|
|
||||||
$hash = md5(strtolower(trim($email)));
|
|
||||||
|
|
||||||
return sprintf('https://www.gravatar.com/avatar/%s?s=%d&d=404', $hash, $size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +1,18 @@
|
|||||||
{% import _self as macros %}
|
{% import _self as macros %}
|
||||||
|
|
||||||
{# Macro to render a form field or static value #}
|
{# Macro to render a form field or static value based on field state #}
|
||||||
{% macro field_or_static(form, fieldName, label, staticValue, options = {}) %}
|
{% macro field_or_static(form, fieldName, label, staticValue, bookingDto, participantIndex, options = {}) %}
|
||||||
{% if form[fieldName] is defined %}
|
{% if form[fieldName] is defined %}
|
||||||
|
{# Field is in form structure - render normally as form input #}
|
||||||
{{ form_row(form[fieldName], options) }}
|
{{ form_row(form[fieldName], options) }}
|
||||||
{% else %}
|
{% elseif is_static_text(fieldName, bookingDto, participantIndex) %}
|
||||||
<div>
|
{# Field excluded from form but should render as static text #}
|
||||||
<label class="font-semibold mb-1 block">{{ label }}</label>
|
<div class="mb-1">
|
||||||
|
<label class="font-semibold block">{{ label }}</label>
|
||||||
<div class="text-sm text-gray-600">{{ staticValue ?: '-' }}</div>
|
<div class="text-sm text-gray-600">{{ staticValue ?: '-' }}</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{# If neither condition is true, don't render anything (truly hidden) #}
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{# Macro to render a field or placeholder with consistent fieldset structure #}
|
{# Macro to render a field or placeholder with consistent fieldset structure #}
|
||||||
@@ -69,14 +72,16 @@
|
|||||||
<div id="participant-form" class="space-y-4">
|
<div id="participant-form" class="space-y-4">
|
||||||
{# Personal data section #}
|
{# Personal data section #}
|
||||||
<div class="grid grid-cols-2 gap-4">
|
<div class="grid grid-cols-2 gap-4">
|
||||||
{{ macros.field_or_static(form, 'firstName', 'Vorname', form.vars.data.participant.firstName) }}
|
{{ macros.field_or_static(form, 'firstName', 'Vorname', form.vars.data.participant.firstName, bookingDto, participantIndex) }}
|
||||||
{{ macros.field_or_static(form, 'lastName', 'Nachname', form.vars.data.participant.lastName) }}
|
{{ macros.field_or_static(form, 'lastName', 'Nachname', form.vars.data.participant.lastName, bookingDto, participantIndex) }}
|
||||||
|
|
||||||
{{ macros.field_or_static(
|
{{ macros.field_or_static(
|
||||||
form,
|
form,
|
||||||
'dateOfBirth',
|
'dateOfBirth',
|
||||||
'Geburtsdatum',
|
'Geburtsdatum',
|
||||||
form.vars.data.participant.dateOfBirth ? form.vars.data.participant.dateOfBirth|date('d.m.Y') : null,
|
form.vars.data.participant.dateOfBirth ? form.vars.data.participant.dateOfBirth|date('d.m.Y') : null,
|
||||||
|
bookingDto,
|
||||||
|
participantIndex,
|
||||||
{
|
{
|
||||||
'attr': {
|
'attr': {
|
||||||
'hx-trigger': 'change',
|
'hx-trigger': 'change',
|
||||||
@@ -88,35 +93,40 @@
|
|||||||
) }}
|
) }}
|
||||||
|
|
||||||
{% set genderLabel = form.vars.data.participant.gender == 'M' ? 'männlich' : (form.vars.data.participant.gender == 'W' ? 'weiblich' : (form.vars.data.participant.gender == 'D' ? 'divers' : null)) %}
|
{% set genderLabel = form.vars.data.participant.gender == 'M' ? 'männlich' : (form.vars.data.participant.gender == 'W' ? 'weiblich' : (form.vars.data.participant.gender == 'D' ? 'divers' : null)) %}
|
||||||
{{ macros.field_or_static(form, 'gender', 'Geschlecht', genderLabel) }}
|
{{ macros.field_or_static(form, 'gender', 'Geschlecht', genderLabel, bookingDto, participantIndex) }}
|
||||||
|
|
||||||
{{ macros.field_or_static(form, 'nationality', 'Nationalität', form.vars.data.participant.nationality) }}
|
{{ macros.field_or_static(form, 'nationality', 'Nationalität', form.vars.data.participant.nationality|map_nationality, bookingDto, participantIndex) }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Contact information #}
|
{# Contact information #}
|
||||||
<div class="grid grid-cols-2 gap-4">
|
<div class="grid grid-cols-2 gap-4">
|
||||||
{{ macros.field_or_static(form, 'email', 'E-Mail', form.vars.data.participant.email) }}
|
{{ macros.field_or_static(form, 'email', 'E-Mail', form.vars.data.participant.email, bookingDto, participantIndex) }}
|
||||||
{{ macros.field_or_static(form, 'mobile', 'Telefon (mobil)', form.vars.data.participant.mobile) }}
|
{{ macros.field_or_static(form, 'mobile', 'Telefon (mobil)', form.vars.data.participant.mobile, bookingDto, participantIndex) }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Address #}
|
{# Address #}
|
||||||
{% if form.address is defined %}
|
{% if form.address is defined %}
|
||||||
|
{# Address field is in form - render as editable form fields #}
|
||||||
<div class="grid grid-cols-2 gap-4">
|
<div class="grid grid-cols-2 gap-4">
|
||||||
{{ form_row(form.address.street) }}
|
{{ form_row(form.address.street) }}
|
||||||
{{ form_row(form.address.postCode) }}
|
{{ form_row(form.address.postCode) }}
|
||||||
{{ form_row(form.address.city) }}
|
{{ form_row(form.address.city) }}
|
||||||
{{ form_row(form.address.country) }}
|
{{ form_row(form.address.country) }}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% elseif is_static_text('address', bookingDto, participantIndex) %}
|
||||||
|
{# Address excluded from form but should render as static text #}
|
||||||
|
{# For applicant (index 0), use booking.applicant.address which has full data from BPN #}
|
||||||
|
{# For other participants, use participants[index].address (may only have country in BPN response) #}
|
||||||
|
{% set addressSource = (0 == participantIndex and bookingDto.booking) ? bookingDto.booking.applicant : bookingDto.participants[participantIndex] %}
|
||||||
<div>
|
<div>
|
||||||
<label class="font-semibold mb-1 block">Adresse</label>
|
<label class="font-semibold mb-1 block">Adresse</label>
|
||||||
{% if form.vars.data.participant.address %}
|
{% if addressSource.address %}
|
||||||
<div class="text-sm text-gray-600">
|
<div class="text-sm text-gray-600">
|
||||||
{% if form.vars.data.participant.address.street %}{{ form.vars.data.participant.address.street }}<br>{% endif %}
|
{% if addressSource.address.street %}{{ addressSource.address.street }}<br>{% endif %}
|
||||||
{% if form.vars.data.participant.address.postCode or form.vars.data.participant.address.city %}
|
{% if addressSource.address.postCode or addressSource.address.city %}
|
||||||
{{ form.vars.data.participant.address.postCode }} {{ form.vars.data.participant.address.city }}<br>
|
{{ addressSource.address.postCode }} {{ addressSource.address.city }}<br>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if form.vars.data.participant.address.country %}{{ form.vars.data.participant.address.country }}{% endif %}
|
{% if addressSource.address.country %}{{ addressSource.address.country|map_country }}{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="text-sm text-gray-600">-</div>
|
<div class="text-sm text-gray-600">-</div>
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
{% use 'form_div_layout.html.twig' %}
|
{% use 'form_div_layout.html.twig' %}
|
||||||
|
|
||||||
|
{# Macro to render a static (read-only) field value with consistent formatting #}
|
||||||
|
{% macro render_static_field(label, value) %}
|
||||||
|
<div class="mb-1">
|
||||||
|
<label class="font-semibold block">{{ label }}</label>
|
||||||
|
<div class="text-sm text-gray-600">{{ value ?: '-' }}</div>
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
{# Macro to render info icon tooltip for descriptions #}
|
{# Macro to render info icon tooltip for descriptions #}
|
||||||
{% macro info_tooltip(description) %}
|
{% macro info_tooltip(description) %}
|
||||||
<div class="pt-px pl-1" {{ stimulus_controller('tooltip') }}>
|
<div class="pt-px pl-1" {{ stimulus_controller('tooltip') }}>
|
||||||
|
|||||||
Reference in New Issue
Block a user