Browse Source

Système Filtres SovBundle

feature/symfony6.1
Fab 3 years ago
parent
commit
75762abfe1
23 changed files with 1064 additions and 50 deletions
  1. +83
    -3
      Controller/AbstractAdminController.php
  2. +8
    -3
      Controller/Ticket/TicketAdminController.php
  3. +67
    -0
      Field/Filter/AssociationFilter.php
  4. +62
    -0
      Field/Filter/CheckboxFilter.php
  5. +77
    -0
      Field/Filter/ChoiceFilter.php
  6. +64
    -0
      Field/Filter/DateFilter.php
  7. +175
    -0
      Field/Filter/FilterManager.php
  8. +37
    -0
      Field/Filter/FilterTrait.php
  9. +43
    -0
      Field/Filter/IntegerFilter.php
  10. +59
    -0
      Field/Filter/TextFilter.php
  11. +140
    -0
      Form/Common/FiltersFormType.php
  12. +1
    -1
      Form/Ticket/TicketFormType.php
  13. +1
    -1
      Form/Ticket/TicketStatusType.php
  14. +1
    -0
      Model/File/FileModel.php
  15. +35
    -31
      Model/Ticket/TicketModel.php
  16. +14
    -5
      Resources/assets/app/adminlte/common/app.common.js
  17. +79
    -0
      Resources/assets/app/adminlte/common/common.js
  18. +15
    -0
      Resources/assets/app/adminlte/index/index.js
  19. +9
    -0
      Resources/assets/app/adminlte/index/index.scss
  20. +2
    -2
      Resources/translations/admin.fr.yaml
  21. +67
    -0
      Resources/views/adminlte/block/table_filters.html.twig
  22. +23
    -2
      Resources/views/adminlte/crud/index.html.twig
  23. +2
    -2
      Translation/TranslatorAdmin.php

+ 83
- 3
Controller/AbstractAdminController.php View File

@@ -12,9 +12,12 @@ use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
use EasyCorp\Bundle\EasyAdminBundle\Config\Actions;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Config\KeyValueStore;
use EasyCorp\Bundle\EasyAdminBundle\Config\Option\EA;
use EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext;
use EasyCorp\Bundle\EasyAdminBundle\Contracts\Controller\CrudControllerInterface;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController as EaAbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto;
use EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto;
use EasyCorp\Bundle\EasyAdminBundle\Dto\SearchDto;
use EasyCorp\Bundle\EasyAdminBundle\Event\AfterCrudActionEvent;
use EasyCorp\Bundle\EasyAdminBundle\Event\AfterEntityUpdatedEvent;
@@ -22,9 +25,11 @@ use EasyCorp\Bundle\EasyAdminBundle\Event\BeforeCrudActionEvent;
use EasyCorp\Bundle\EasyAdminBundle\Event\BeforeEntityDeletedEvent;
use EasyCorp\Bundle\EasyAdminBundle\Exception\ForbiddenActionException;
use EasyCorp\Bundle\EasyAdminBundle\Exception\InsufficientEntityPermissionException;
use EasyCorp\Bundle\EasyAdminBundle\Factory\ControllerFactory;
use EasyCorp\Bundle\EasyAdminBundle\Factory\EntityFactory;
use EasyCorp\Bundle\EasyAdminBundle\Factory\FilterFactory;
use EasyCorp\Bundle\EasyAdminBundle\Factory\PaginatorFactory;
use EasyCorp\Bundle\EasyAdminBundle\Field\AssociationField;
use EasyCorp\Bundle\EasyAdminBundle\Field\FormField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextareaField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
@@ -36,16 +41,25 @@ use Lc\SovBundle\Doctrine\Extension\SortableInterface;
use Lc\SovBundle\Doctrine\Extension\TranslatableInterface;
use Lc\SovBundle\Doctrine\Extension\TreeInterface;
use Lc\SovBundle\Field\CollectionField;
use Lc\SovBundle\Field\Filter\FilterManager;
use Lc\SovBundle\Form\Common\FiltersFormType;
use Lc\SovBundle\Form\Common\PositionType;
use Lc\SovBundle\Translation\TranslatorAdmin;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Session\SessionInterface;


abstract class AbstractAdminController extends EaAbstractCrudController
{
protected $filtersForm;

public static function getSubscribedServices()
{
return array_merge(
@@ -55,6 +69,7 @@ abstract class AbstractAdminController extends EaAbstractCrudController
'request' => RequestStack::class,
'em' => EntityManagerInterface::class,
'translator_admin' => TranslatorAdmin::class,
'filter_manager' => FilterManager::class,
]
);
}
@@ -66,6 +81,16 @@ abstract class AbstractAdminController extends EaAbstractCrudController
$this->overrideEntitiesActions($responseParameters->get('entities'));
if (Crud::PAGE_INDEX === $responseParameters->get('pageName')) {
$responseParameters->set('fields', $this->configureFields('index'));

if ($this->filtersForm === null) {
die('nncncd');
//TODO delete under code
$options['fields'] = $responseParameters->get('fields');
$options['entity_class'] = $this->getEntityFqcn();
$options['entity_name'] = $responseParameters->get('entity')->getName();
$this->filtersForm = $this->createForm(FiltersFormType::class, null, $options);
}
$responseParameters->set('filters_form', $this->filtersForm);
}

return $responseParameters;
@@ -291,6 +316,25 @@ abstract class AbstractAdminController extends EaAbstractCrudController
}
}

$this->filtersForm = $this->createForm(
FiltersFormType::class,
null,
array(
'fields' => $fields,
'entity_dto' => $entityDto,
'entity_class' => $this->getEntityFqcn(),
'entity_name' => $entityDto->getName(),
)
);

$filterManager = $this->get('filter_manager');

$this->filtersForm->handleRequest($searchDto->getRequest());
/*if (($this->filtersForm->isSubmitted() && $this->filtersForm->isValid())) {

}*/
$filterManager->handleFiltersForm($queryBuilder, $this->filtersForm, $fields, $entityDto);


return $queryBuilder;
}
@@ -477,9 +521,6 @@ abstract class AbstractAdminController extends EaAbstractCrudController
'label' => $this->get('translator_admin')->transAction('delete'),
]
);



}

public function buildDetailActions(Actions $actions): void
@@ -616,6 +657,45 @@ abstract class AbstractAdminController extends EaAbstractCrudController
}
}

public function autocompleteFilter(AdminContext $context): JsonResponse
{
$queryBuilder = $this->createIndexQueryBuilder(
$context->getSearch(),
$context->getEntity(),
FieldCollection::new([]),
FilterCollection::new()
);
$autocompleteContext = $context->getRequest()->get(AssociationField::PARAM_AUTOCOMPLETE_CONTEXT);

/** @var CrudControllerInterface $controller */
$controller = $this->get(ControllerFactory::class)->getCrudControllerInstance(
$autocompleteContext[EA::CRUD_CONTROLLER_FQCN],
Action::INDEX,
$context->getRequest()
);
/** @var FieldDto $field */
$field = FieldCollection::new(
$controller->configureFields($autocompleteContext['originatingPage'])
)->getByProperty($autocompleteContext['propertyName']);

$filterManager = $this->get('filter_manager');

$filterManager->applyFilter($queryBuilder, $field, $context->getRequest()->query->get('q'));
if ($filterManager->isRelationField($field->getProperty())) {
$queryBuilder->select($autocompleteContext['propertyName']);
} else {
$queryBuilder->select('entity.'.$autocompleteContext['propertyName']);
}

$responses = array();
foreach ($queryBuilder->getQuery()->getArrayResult() as $result) {
$responses[] = array_values($result)[0];
}


return JsonResponse::fromJsonString(json_encode($responses));
}


}


+ 8
- 3
Controller/Ticket/TicketAdminController.php View File

@@ -21,6 +21,7 @@ use Lc\SovBundle\Form\Ticket\TicketStatusType;
use Lc\SovBundle\Model\Ticket\TicketInterface;
use Lc\SovBundle\Controller\AbstractAdminController;
use Lc\SovBundle\Model\Ticket\TicketModel;
use Lc\SovBundle\Translation\TranslatorAdmin;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;

@@ -30,15 +31,18 @@ class TicketAdminController extends AbstractAdminController
protected $ticketMessageFactory;
protected $adminUrlGenerator;
protected $em;
protected $translatorAdmin;

public function __construct(
TicketFactoryInterface $ticketFactory,
TicketMessageFactory $ticketMessageFactory,
AdminUrlGenerator $adminUrlGenerator
AdminUrlGenerator $adminUrlGenerator,
TranslatorAdmin $translatorAdmin
) {
$this->ticketFactory = $ticketFactory;
$this->ticketMessageFactory = $ticketMessageFactory;
$this->adminUrlGenerator = $adminUrlGenerator;
$this->translatorAdmin = $translatorAdmin;
}

public static function getEntityFqcn(): string
@@ -77,12 +81,13 @@ class TicketAdminController extends AbstractAdminController
ChoiceField::new('type')
->autocomplete()
->setChoices(
TicketModel::getChoicesType()
$this->translatorAdmin->transChoices(TicketModel::getTypeChoices(), 'Ticket', 'type')
),
ChoiceField::new('status')
->autocomplete()
->setChoices(
TicketModel::getChoicesStatus()
$this->translatorAdmin->transChoices(TicketModel::getStatusChoices(), 'Ticket', 'status')

)
->setTemplatePath('@LcSov/admin/ticket/index_status.html.twig')
->hideOnForm(),

+ 67
- 0
Field/Filter/AssociationFilter.php View File

@@ -0,0 +1,67 @@
<?php

namespace Lc\SovBundle\Field\Filter;

use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\QueryBuilder;
use EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;

/**
* @author La clic ! <contact@laclic.fr>
*/
class AssociationFilter
{
use FilterTrait;


public function buildProperty(FormBuilderInterface $builder, FieldDto $fieldDto, $options = array())
{
$targetEntity = $options['entity_dto']->getPropertyMetadata($fieldDto->getProperty())->get('targetEntity');

$classImplements = class_implements($targetEntity);
$builder->add(
$fieldDto->getProperty(),
EntityType::class,
array(
'class' => $targetEntity,
'placeholder' => '--',
'query_builder' => function (EntityRepository $repo) use ($classImplements) {
return $repo->createQueryBuilder('entity');
},
'required' => false,
'attr' => array(
'class' => 'select2 input-sm',
'form' => 'filters-form',
),


)
);
}

public function applyFilter(QueryBuilder $queryBuilder, string $fieldProperty, string $filteredValue = null)
{
if ($filteredValue !== null) {
$queryBuilder->andWhere('entity.'.$fieldProperty.' = :'.$fieldProperty.'');
$queryBuilder->setParameter($fieldProperty, $filteredValue);
/* //TODO Faut généraliser avec TreeInterface, ça ne doit pas être ici
if ($field['property'] == 'productCategories') {

$queryBuilder->andWhere(':' . $field['property'] . ' MEMBER OF entity.' . $field['property'] . ' OR product_categories.parent = :' . $field['property']);
$queryBuilder->setParameter($field['property'], $filter);

if ($field['type_options']['multiple']) {
$queryBuilder->andWhere(':' . $field['property'] . ' MEMBER OF entity.' . $field['property'] . '');
} else {
}

if ($filter instanceof TreeInterface && $filter->getParent() == null) {
$queryBuilder->setParameter($field['property'], array_merge(array($filter), $filter->getChildrens()->toArray()));
} else {
}*/
}
}

}

+ 62
- 0
Field/Filter/CheckboxFilter.php View File

@@ -0,0 +1,62 @@
<?php

namespace Lc\SovBundle\Field\Filter;

use Doctrine\ORM\QueryBuilder;
use EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto;
use Lc\SovBundle\Translation\TranslatorAdmin;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;

use function Symfony\Component\String\u;


/**
* @author La clic ! <contact@laclic.fr>
*/
class CheckboxFilter
{
use FilterTrait;

public function buildProperty(FormBuilderInterface $builder, FieldDto $fieldDto, $options = array())
{
$builder->add($fieldDto->getProperty(), ChoiceType::class, array(
'choices'=> array(
'Oui' => 1,
'Non' => 0,
),
'placeholder'=> '--',
'required'=>false,
'attr'=>array(
'class'=> 'select2 input-sm',
'form'=> 'filters-form'
)
));
}

public function applyFilter(QueryBuilder $queryBuilder, string $fieldProperty, string $filteredValue= null)
{
if ($filteredValue !== null) {
if ($this->isRelationField($fieldProperty)) {
$aliasRelation = $this->getFieldPropertyRelationAlias($fieldProperty);
if (array_search($aliasRelation, $queryBuilder->getAllAliases()) === false) {
$queryBuilder->innerJoin('entity.'.$aliasRelation, $aliasRelation);
}
$queryBuilder->andWhere(
$fieldProperty.' = :'.$this->getFieldPropertySnake($fieldProperty).''
);
$queryBuilder->setParameter(
$this->getFieldPropertySnake($fieldProperty),
'%'.$filteredValue.'%'
);
} else {
$queryBuilder->andWhere(
'entity.'.$fieldProperty.' = :'.$fieldProperty.''
);
$queryBuilder->setParameter($fieldProperty, '%'.$filteredValue.'%');
}
dump($queryBuilder);
}
}

}

+ 77
- 0
Field/Filter/ChoiceFilter.php View File

@@ -0,0 +1,77 @@
<?php

namespace Lc\SovBundle\Field\Filter;

use Doctrine\ORM\QueryBuilder;
use EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto;
use Lc\SovBundle\Translation\TranslatorAdmin;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;

use function Symfony\Component\String\u;


/**
* @author La clic ! <contact@laclic.fr>
*/
class ChoiceFilter
{
use FilterTrait;

protected $translatorAdmin;

public function __construct(?TranslatorAdmin $translatorAdmin = null)
{
$this->translatorAdmin = $translatorAdmin;
}

public function buildProperty(FormBuilderInterface $builder, FieldDto $fieldDto, $options = array())
{
$entity = new $options['entity_class'];
$choicesFct = 'get'.u($fieldDto->getProperty())->title()->toString().'Choices';

if (method_exists($entity, $choicesFct)) {
$builder->add(
$fieldDto->getProperty(),
ChoiceType::class,
array(
'required' => false,
'choices' => $this->translatorAdmin->transChoices(
$entity->$choicesFct(),
$options['entity_name'],
$fieldDto->getProperty()
),
'attr' => array(
'class' => 'select2 input-sm',
'form' => 'filters-form',
),
)
);
}
}

public function applyFilter(QueryBuilder $queryBuilder, string $fieldProperty, string $filteredValue = null)
{
if ($filteredValue !== null) {
if ($this->isRelationField($fieldProperty)) {
$aliasRelation = $this->getFieldPropertyRelationAlias($fieldProperty);
if (array_search($aliasRelation, $queryBuilder->getAllAliases()) === false) {
$queryBuilder->innerJoin('entity.'.$aliasRelation, $aliasRelation);
}
$queryBuilder->andWhere(
$fieldProperty.' LIKE :'.$this->getFieldPropertySnake($fieldProperty).''
);
$queryBuilder->setParameter(
$this->getFieldPropertySnake($fieldProperty),
'%'.$filteredValue.'%'
);
} else {
$queryBuilder->andWhere(
'entity.'.$fieldProperty.' LIKE :'.$fieldProperty.''
);
$queryBuilder->setParameter($fieldProperty, '%'.$filteredValue.'%');
}
}
}

}

+ 64
- 0
Field/Filter/DateFilter.php View File

@@ -0,0 +1,64 @@
<?php

namespace Lc\SovBundle\Field\Filter;

use Doctrine\ORM\QueryBuilder;
use EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\FormBuilderInterface;

/**
* @author La clic ! <contact@laclic.fr>
*/
class DateFilter
{
use FilterTrait;

public function buildProperty(FormBuilderInterface $builder, FieldDto $fieldDto, $options = array())
{
$builder->add(
$builder->create(
str_replace('.', '_', $fieldDto->getProperty()),
FormType::class,
array('inherit_data' => true)
)
->add(
'dateStart',
DateType::class,
array(
'widget' => 'single_text',
'required' => false,
)
)
->add(
'dateEnd',
DateType::class,
array(
'widget' => 'single_text',
'required' => false,

)
)
);
}

public function applyFilter(
QueryBuilder $queryBuilder,
string $fieldProperty,
\DateTime $dateStart = null,
\DateTime $dateEnd = null
) {
if ($dateStart) {
$queryBuilder->andWhere(
'entity.'.$fieldProperty.' >= :dateStart'
)->setParameter('dateStart', $dateStart);
}
if ($dateEnd) {
$dateEnd->setTime(23, 59, 59);
$queryBuilder->andWhere(
'entity.'.$fieldProperty.' <= :dateEnd'
)->setParameter('dateEnd', $dateEnd);
}
}
}

+ 175
- 0
Field/Filter/FilterManager.php View File

@@ -0,0 +1,175 @@
<?php

namespace Lc\SovBundle\Field\Filter;

use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\QueryBuilder;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Contracts\Field\FieldInterface;
use EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto;
use EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Form;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Session\SessionInterface;


/**
* @author La clic ! <contact@laclic.fr>
*/
class FilterManager
{
protected $em;

use FilterTrait;

public function __construct(SessionInterface $session, EntityManagerInterface $entityManager)
{
$this->session = $session;
$this->em = $entityManager;
}


public function handleFiltersForm(QueryBuilder $queryBuilder, Form $filtersForm, $fields, EntityDto $entityDto)
{
foreach ($fields as $field) {
if ($field instanceof FieldInterface) {
$fieldDto = $field->getAsDto();
} else {
$fieldDto = $field;
}
if ($fieldDto->isDisplayedOn(Crud::PAGE_INDEX)) {
if ($filtersForm->has($this->getFieldPropertySnake($fieldDto->getProperty()))) {



if ($fieldDto->getFormType() === DateTimeType::class || $fieldDto->getFormType(
) === DateType::class) {
$filteredValue['dateStart'] = $this->getFilteredValue(
$filtersForm,
$entityDto->getFqcn(),
$fieldDto->getProperty(),
'dateStart'
);
$filteredValue['dateEnd'] = $this->getFilteredValue(
$filtersForm,
$entityDto->getFqcn(),
$fieldDto->getProperty(),
'dateEnd'
);
}else{
$filteredValue = $this->getFilteredValue(
$filtersForm,
$entityDto->getFqcn(),
$fieldDto->getProperty()
);
}
$this->applyFilter($queryBuilder, $fieldDto, $filteredValue);
}
}
}
}

public function applyFilter(QueryBuilder $queryBuilder, FieldDto $fieldDto, $filteredValue)
{
switch ($fieldDto->getFormType()) {
case CheckboxType::class:
$checkboxFilter = new CheckboxFilter();
$checkboxFilter->applyFilter($queryBuilder, $fieldDto->getProperty(), $filteredValue);
break;
case ChoiceType::class:
$choiceFilter = new ChoiceFilter();
$choiceFilter->applyFilter($queryBuilder, $fieldDto->getProperty(), $filteredValue);
break;
case IntegerType::class:
$integerFilter = new IntegerFilter();
$integerFilter->applyFilter($queryBuilder, $fieldDto->getProperty(), $filteredValue);
break;
case TextType::class:
$textFilter = new TextFilter();
$textFilter->applyFilter($queryBuilder, $fieldDto->getProperty(), $filteredValue);
break;
case EntityType::class:
$textFilter = new AssociationFilter();
$textFilter->applyFilter($queryBuilder, $fieldDto->getProperty(), $filteredValue);
break;
case DateTimeType::class:
case DateType::class:
$textFilter = new DateFilter();
$textFilter->applyFilter(
$queryBuilder,
$fieldDto->getProperty(),
$filteredValue['dateStart'],
$filteredValue['dateEnd']
);
break;
}
}

public function getFilteredValue(
Form $filtersForm,
string $entityFqcn,
string $field,
string $dateExtraField = null
) {
$field = $this->getFieldPropertySnake($field);
$sessionParam = $entityFqcn.$field.$dateExtraField;

$formField = $this->getFormField($filtersForm, $field, $dateExtraField);

$value = $formField->getData();

//Il existe une valeur posté dans le formulaire
if ($value !== null) {
$this->session->set($sessionParam, $value);
return $value;
}

//action reset
if ($filtersForm->get('reset')->getData() == 'clearAll') {
$this->session->remove($sessionParam);
return null;
}

//Récupération des valeurs stocké en sessions si le forrmFilters n'a pas été posté
if ($this->session->get($sessionParam) && !$filtersForm->isSubmitted() && $formField) {
$value = $this->session->get($sessionParam);

//Champ date
if ($formField->getConfig()->getOption('input') == 'datetime') {
$filtersForm->get($field)->get($dateExtraField)->setData($value);
//Champ association
} elseif ($formField->getConfig()->getOption('class')) {
$valFormated = $this->em->getRepository(
$formField->getConfig()->getOption('class')
)->find($value);
$filtersForm->get($field)->setData($valFormated);
} else {
//Champ noramux
$filtersForm->get($field)->setData($value);
}

return $value;
}

}

public
function getFormField(
Form $filtersForm,
string $field,
string $extraField = null
): Form {
if ($extraField) {
return $filtersForm->get($field)->get($extraField);
} else {
return $filtersForm->get($field);
}
}
}

+ 37
- 0
Field/Filter/FilterTrait.php View File

@@ -0,0 +1,37 @@
<?php

namespace Lc\SovBundle\Field\Filter;


/**
* @author La clic ! <contact@laclic.fr>
*/
trait FilterTrait
{

public function getFieldPropertySnake(string $fieldName): string
{
return str_replace('.', '_', $fieldName);
}

public function getFieldPropertyBase(string $fieldName): string
{
return str_replace('_', '.', $fieldName);
}

public function isRelationField(string $fieldName, string $needle = "."): bool
{
return strpos($fieldName, $needle) !== false;
}

public function getFieldPropertyWithoutRelation(string $fieldName, string $needle = '.'): string
{
return substr($fieldName, strpos($fieldName, $needle) + 1);
}

public function getFieldPropertyRelationAlias(string $fieldName, string $needle = '.'): string
{
return substr($fieldName, 0, strpos($fieldName, $needle));
}

}

+ 43
- 0
Field/Filter/IntegerFilter.php View File

@@ -0,0 +1,43 @@
<?php

namespace Lc\SovBundle\Field\Filter;

use Doctrine\ORM\QueryBuilder;
use EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\FormBuilderInterface;

/**
* @author La clic ! <contact@laclic.fr>
*/
class IntegerFilter
{
use FilterTrait;


public function buildProperty(FormBuilderInterface $builder, FieldDto $fieldDto, $options = array())
{
$builder->add(
str_replace('.', '_', $fieldDto->getProperty()),
IntegerType::class,
array(
'required' => false,
'attr' => array(
'class' => ' input-sm',
'form' => 'filters-form',
),
)
);
}

public function applyFilter(QueryBuilder $queryBuilder, string $fieldProperty, string $filteredValue= null)
{
if ($filteredValue !== null) {
$queryBuilder->andWhere(
'entity.'.$fieldProperty.' = :'.$fieldProperty.''
);
$queryBuilder->setParameter($fieldProperty, $filteredValue);
}
}

}

+ 59
- 0
Field/Filter/TextFilter.php View File

@@ -0,0 +1,59 @@
<?php

namespace Lc\SovBundle\Field\Filter;

use Doctrine\ORM\QueryBuilder;
use EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;

/**
* @author La clic ! <contact@laclic.fr>
*/
class TextFilter
{
use FilterTrait;


public function buildProperty(FormBuilderInterface $builder, FieldDto $fieldDto, $options = array())
{
$builder->add(
str_replace('.', '_', $fieldDto->getProperty()),
TextType::class,
array(
'required' => false,
'attr' => array(
'class' => ' input-sm',
'form' => 'filters-form',
),
)
);
}

public function applyFilter(QueryBuilder $queryBuilder, string $fieldProperty, string $filteredValue= null)
{

if ($filteredValue !== null) {
if ($this->isRelationField($fieldProperty)) {

$aliasRelation = $this->getFieldPropertyRelationAlias($fieldProperty);
if (array_search($aliasRelation, $queryBuilder->getAllAliases()) === false) {
$queryBuilder->innerJoin('entity.'.$aliasRelation, $aliasRelation);
}
$queryBuilder->andWhere(
$fieldProperty.' LIKE :'.$this->getFieldPropertySnake($fieldProperty).''
);
$queryBuilder->setParameter(
$this->getFieldPropertySnake($fieldProperty),
'%'.$filteredValue.'%'
);
} else {
$queryBuilder->andWhere(
'entity.'.$fieldProperty.' LIKE :'.$fieldProperty.''
);
$queryBuilder->setParameter($fieldProperty, '%'.$filteredValue.'%');
}
}
}

}

+ 140
- 0
Form/Common/FiltersFormType.php View File

@@ -0,0 +1,140 @@
<?php

namespace Lc\SovBundle\Form\Common;

use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Contracts\Field\FieldInterface;
use Lc\SovBundle\Field\Filter\AssociationFilter;
use Lc\SovBundle\Field\Filter\CheckboxFilter;
use Lc\SovBundle\Field\Filter\ChoiceFilter;
use Lc\SovBundle\Field\Filter\DateFilter;
use Lc\SovBundle\Field\Filter\FilterManager;
use Lc\SovBundle\Field\Filter\IntegerFilter;
use Lc\SovBundle\Field\Filter\TextFilter;
use Lc\SovBundle\Translation\TranslatorAdmin;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ButtonType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Contracts\Translation\TranslatorInterface;

use function Symfony\Component\String\u;


class FiltersFormType extends AbstractType
{
protected $translatorAdmin;

public function __construct(TranslatorAdmin $translatorAdmin)
{
$this->translatorAdmin = $translatorAdmin;
}


public function buildForm(FormBuilderInterface $builder, array $options)
{
foreach ($options['fields'] as $field) {
if ($field instanceof FieldInterface) {
$fieldDto = $field->getAsDto();
} else {
$fieldDto = $field;
}
if ($fieldDto->isDisplayedOn(Crud::PAGE_INDEX)) {
switch ($fieldDto->getFormType()) {
case CheckboxType::class:
$checkboxFilter = new CheckboxFilter();
$checkboxFilter->buildProperty($builder, $fieldDto);

break;
case ChoiceType::class:
$choiceFilter = new ChoiceFilter($this->translatorAdmin);
$choiceFilter->buildProperty($builder, $fieldDto, $options);
break;
case IntegerType::class:
$integerFilter = new IntegerFilter();
$integerFilter->buildProperty($builder, $fieldDto);

break;
case TextType::class:
$textFilter = new TextFilter();
$textFilter->buildProperty($builder, $fieldDto);
break;
case DateTimeType::class:
case DateType::class:
$textFilter = new DateFilter();
$textFilter->buildProperty($builder, $fieldDto);
break;
case EntityType::class:
$associationFilter = new AssociationFilter();
$associationFilter->buildProperty($builder, $fieldDto, $options);
break;
case 'dateinterval':

break;
case 'float':

break;
}
}
}
$builder->add(
'action_apply',
SubmitType::class,
array(
'label_html'=> true,
'label'=> '<i class="fa fa-search"></i>',
'attr' => array(
'class' => 'btn btn-sm btn-info',
'form' => 'filters-form',
'data-toggle'=>"tooltip",
'title'=> $this->translatorAdmin->transAction("apply"),
'aria-label'=> $this->translatorAdmin->transAction("apply")
),
)
);
$builder->add(
'action_reset',
ButtonType::class,
array(
'label_html'=> true,
'label'=> '<i class="fa fa-eraser"></i>',
'attr' => array(
'class' => 'btn btn-sm btn-warning lc-reset-filters',
'form' => 'filters-form',
'data-toggle'=>"tooltip",
'title'=> $this->translatorAdmin->transAction("reset"),
'aria-label'=> $this->translatorAdmin->transAction("reset")
),
)
);
$builder->add('reset', HiddenType::class);
}

public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(
[
'label' => false,
'csrf_protection' => false,
'entity_dto' => null,
//'translation_domain' => 'lcshop',
'fields' => false,
'entity_name' => false,
'entity_class' => false,
//'entityClass' => false
]
);
}
}

+ 1
- 1
Form/Ticket/TicketFormType.php View File

@@ -49,7 +49,7 @@ class TicketFormType extends AbstractType
ChoiceType::class,
[
'label' => 'Type',
'choices' => $this->translatorAdmin->transChoices($entityName::getChoicesType(),TicketInterface::class),
'choices' => $this->translatorAdmin->transChoices($entityName::getTypeChoices(),'Ticket', 'type'),
]
);


+ 1
- 1
Form/Ticket/TicketStatusType.php View File

@@ -29,7 +29,7 @@ class TicketStatusType extends AbstractType
'status',
ChoiceType::class,
[
'choices' => $this->translatorAdmin->transChoices(TicketModel::getChoicesStatus(),TicketModel::class),
'choices' => $this->translatorAdmin->transChoices(TicketModel::getStatusChoices(),'Ticket', 'status'),
'required' => true,
'expanded' => true,
'label' => false,

+ 1
- 0
Model/File/FileModel.php View File

@@ -26,6 +26,7 @@ abstract class FileModel implements SortableInterface, BlameableInterface, Times
use SortableTrait;

/**
* @Gedmo\Translatable
* @ORM\Column(type="string", length=255, nullable=true)
*/
protected $path;

+ 35
- 31
Model/Ticket/TicketModel.php View File

@@ -22,6 +22,35 @@ abstract class TicketModel extends AbstractLightEntity implements TicketInterfac
const TICKET_STATUS_BEING_PROCESSED = 'being-processed';
const TICKET_STATUS_CLOSED = 'closed';


public function getTypeChoices(): array
{
return [
TicketModel::TYPE_GENERAL_QUESTION,
TicketModel::TYPE_TECHNICAL_PROBLEM,
];
}

public function getStatusChoices(): array
{
return [
TicketModel::TICKET_STATUS_OPEN,
TicketModel::TICKET_STATUS_BEING_PROCESSED,
TicketModel::TICKET_STATUS_CLOSED,
];
}


public function getTypeLabel(): string
{
return 'entity.Ticket.fields.typeChoices.'.$this->getType();
}

public function getStatusLabel(): string
{
return 'entity.Ticket.statuChoices.'.$this->getStatus();
}

/**
* @Gedmo\Blameable(on="create")
* @ORM\ManyToOne(targetEntity="Lc\SovBundle\Model\User\UserInterface")
@@ -97,7 +126,7 @@ abstract class TicketModel extends AbstractLightEntity implements TicketInterfac
if ($this->getUser()) {
return $this->getUser()->getName();
} else {
return strtoupper($this->getVisitorLastname()) . ' ' . $this->getVisitorFirstname();
return strtoupper($this->getVisitorLastname()).' '.$this->getVisitorFirstname();
}
}

@@ -105,10 +134,10 @@ abstract class TicketModel extends AbstractLightEntity implements TicketInterfac
{
$user = $this->getUser();
if ($user) {
return '#' . $user->getId() . ' ' . $user->getName() . ' ' . $user->getEmail();
return '#'.$user->getId().' '.$user->getName().' '.$user->getEmail();
} else {
return strtoupper($this->getVisitorLastname()) . ' ' . $this->getVisitorFirstname(
) . ' ' . $this->getVisitorEmail();
return strtoupper($this->getVisitorLastname()).' '.$this->getVisitorFirstname().' '.$this->getVisitorEmail(
);
}
}

@@ -123,8 +152,8 @@ abstract class TicketModel extends AbstractLightEntity implements TicketInterfac

public function getVisitorInfos()
{
return strtoupper($this->getVisitorLastname()) . ' ' . $this->getVisitorFirstname(
) . ' (' . $this->getVisitorEmail() . ')';
return strtoupper($this->getVisitorLastname()).' '.$this->getVisitorFirstname().' ('.$this->getVisitorEmail(
).')';
}

public function getLastMessage()
@@ -144,18 +173,6 @@ abstract class TicketModel extends AbstractLightEntity implements TicketInterfac
return $this;
}

public function getTypeLabel(): string
{
return 'entity.Ticket.fields.typeOptions.' . $this->getType();
}

public function getChoicesType(): array
{
return [
'entity.Ticket.fields.typeOptions.' . TicketModel::TYPE_GENERAL_QUESTION => TicketModel::TYPE_GENERAL_QUESTION,
'entity.Ticket.fields.typeOptions.' . TicketModel::TYPE_TECHNICAL_PROBLEM => TicketModel::TYPE_TECHNICAL_PROBLEM,
];
}

public function getStatus(): ?string
{
@@ -169,19 +186,6 @@ abstract class TicketModel extends AbstractLightEntity implements TicketInterfac
return $this;
}

public function getStatusLabel(): string
{
return 'entity.Ticket.statusOptions.' . $this->getStatus();
}

public function getChoicesStatus(): array
{
return [
'entity.Ticket.fields.statusOptions.' . TicketModel::TICKET_STATUS_OPEN => TicketModel::TICKET_STATUS_OPEN,
'entity.Ticket.fields.statusOptions.' . TicketModel::TICKET_STATUS_BEING_PROCESSED => TicketModel::TICKET_STATUS_BEING_PROCESSED,
'entity.Ticket.fields.statusOptions.' . TicketModel::TICKET_STATUS_CLOSED => TicketModel::TICKET_STATUS_CLOSED,
];
}

public function getSubject(): ?string
{

+ 14
- 5
Resources/assets/app/adminlte/common/app.common.js View File

@@ -7,19 +7,28 @@ global.$ = global.jQuery = $;
import 'adminlte-js' ;

// Bootstrap
import 'adminlte-plugin/bootstrap/js/bootstrap.min.js';
import 'bootstrap/dist/js/bootstrap.min.js';

// Bootstrap - autocomplete
import 'bootstrap-autocomplete';

// Select2
import 'adminlte-plugin/select2/js/select2.min.js';
import 'adminlte-plugin/select2/js/i18n/fr.js';
import 'adminlte-plugin/select2/css/select2.min.css';
import 'adminlte-plugin/select2-bootstrap4-theme/select2-bootstrap4.min.css';
import 'select2/js/select2.min.js';
import 'select2/js/i18n/fr.js';
import 'select2/css/select2.min.css';
import 'select2-bootstrap4-theme';

// Toastr
import toastr from 'toastr/toastr.js' ;
import 'toastr/toastr.scss' ;
global.toastr = toastr ;


// DaterangePicker
import 'moment' ;
import 'daterangepicker/daterangepicker.js' ;
import 'daterangepicker/daterangepicker.css' ;

// Tools
import { Tools } from '../../../tools/tools.js';
global.Tools = Tools;

+ 79
- 0
Resources/assets/app/adminlte/common/common.js View File

@@ -2,6 +2,12 @@
/* Notifications */
//Notification.init() ;


setDateRange();

setAutoCompleteField();


/* Tooltip */
$('[data-toggle="tooltip"]').tooltip();

@@ -74,3 +80,76 @@ $('.action-delete').on('click', function (e) {
});
});


function setDateRange(){


$('.date-time-range, .date-range').each(function (i, picker) {
options = {
autoUpdateInput: false,
locale: {
"format": "DD/MM/YY",
"separator": " - ",
"applyLabel": "Appliquer",
"cancelLabel": "Annuler",
"fromLabel": "Du",
"toLabel": "au",
"customRangeLabel": "Custom",
"daysOfWeek": ["Di", "Lu", "Ma", "Me", "Je", "Ve", "Sa"],
"monthNames": ["Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre"],
"firstDay": 1
}
};
if ($(picker).hasClass('date-time-range')){
options = Object.assign(options, {
timePicker: true,
timePickerIncrement: 30,
timePicker24Hour: true,
locale: {
"format": "DD/MM/YYYY HH:mm",
}
});
}
if ($(picker).nextAll('.date-time-range-fields').find('.date-start').val()) {
options.startDate = new Date($(picker).nextAll('.date-time-range-fields').find('.date-start').val());
options.autoUpdateInput = true;
}
if ($(picker).nextAll('.date-time-range-fields').find('.date-end').val()) {
options.endDate = new Date($(picker).nextAll('.date-time-range-fields').find('.date-end').val());
options.autoUpdateInput = true;
}
$(picker).daterangepicker(options);
$(picker).on('apply.daterangepicker', function (ev, pickerElm) {
if ($(picker).hasClass('date-time-range')) {
$(this).val(pickerElm.startDate.format('DD/MM/YY HH:mm') + ' - ' + pickerElm.endDate.format(options.locale.format));
}else{
$(this).val(pickerElm.startDate.format('DD/MM/YY') + ' - ' + pickerElm.endDate.format(options.locale.format));
}
if ($(picker).hasClass('date-time-range')) {
$(picker).nextAll('.date-time-range-fields').find('.date-start').val(pickerElm.startDate.format('YYYY-MM-DD HH:mm'));
$(picker).nextAll('.date-time-range-fields').find('.date-end').val(pickerElm.endDate.format('YYYY-MM-DD HH:mm'));
}else{
$(picker).nextAll('.date-time-range-fields').find('.date-start').val(pickerElm.startDate.format('YYYY-MM-DD'));
$(picker).nextAll('.date-time-range-fields').find('.date-end').val(pickerElm.endDate.format('YYYY-MM-DD'));
}
});
});

}


function setAutoCompleteField() {
var autocompleteFields = $('[data-lc-autocomplete-url]');

autocompleteFields.each(function () {
var $this = $(this),
url = $this.data('lc-autocomplete-url');

$this.autoComplete({
noResultsText: 'Aucun résultat n\'a été trouvé.',
resolverSettings: {
url: url
}
});
});
}

+ 15
- 0
Resources/assets/app/adminlte/index/index.js View File

@@ -3,6 +3,7 @@
$(document).ready(function() {
lcCrudIndexToggle() ;
lcCrudIndexBatchActions() ;
lcCrudIndexInitFilter();
}) ;

function lcCrudIndexToggle() {
@@ -132,4 +133,18 @@ function lcCrudIndexBatchActions() {
$form.submit();
});
});
}

function lcCrudIndexInitFilter() {
$('#filters_form_reset').val('');

$('.lc-reset-filters').on('click', function (e) {
//e.preventDefault();
$(this).parents('.table-filters-line').find('select,input').val('').trigger('change');
$('#filters_form_reset').val('clearAll');
Tools.log($(this).prop('form'));
$(this).prop('form').submit();
//$(this).parents('form').submit();
})

}

+ 9
- 0
Resources/assets/app/adminlte/index/index.scss View File

@@ -6,3 +6,12 @@ table.table {
white-space: nowrap;
}
}

.table-filters-line th {
font-weight: 400;
position: relative;
}

.table td, .table th {
padding: 0.35rem;
}

+ 2
- 2
Resources/translations/admin.fr.yaml View File

@@ -76,13 +76,13 @@ entity:
visitorEmail: E-mail
subject: Sujet
type: Type
typeOptions:
typeChoices:
general-question: Questions générales
technical-problem: Problème technique
product-unavailable: Produit manquant
product-error: Erreur sur un produit
lastMessage: Dernier message
statusOptions:
statusChoices:
open: Ouvert
being-processed: En attente
closed: Fermé

+ 67
- 0
Resources/views/adminlte/block/table_filters.html.twig View File

@@ -0,0 +1,67 @@
{% set filters_form_are_not_empty = false %}
{% if filters_form is defined %}
<tr class="table-filters-line">
{% if has_batch_actions %}
<th></th>
{% endif %}
{% for field in fields ?? [] %}

{% set field = field.getAsDto() %}
{% if field.isDisplayedOn('index') %}
<th>
{% set field_property= field.property|replace({'.': "_"}) %}
{% if filters_form[field_property] is defined %}

{% set form_field = filters_form[field_property] %}

{% if form_field.vars.value is not null and form_field.vars.value is not empty %}{% set filters_form_are_not_empty = true %}{% endif %}
{% if "DateType" in field.formType or "DateTimeType" in field.formType %}
<div class="input-group input-group-sm">
<input type="text"
class="form-control input-sm float-right date-range">
<div class="hidden date-time-range-fields"
style="display: none;">
{{ form_widget(form_field['dateStart'], {"attr" : {'class' : 'date-start', 'form': 'filters-form'}}) }}
{{ form_widget(form_field['dateEnd'], {"attr" : {'class' : 'date-end', 'form': 'filters-form'}}) }}
</div>
</div>
{% else %}

<div class="form-widget input-group-sm">
{% set url = ea_url()
.setController(ea.crudControllers.findCrudFqcnByEntityFqcn(ea.entity.fqcn))
.setAction('autocompleteFilter')
.setEntityId(null)
.set('page',1)
.unset('sort')
.set('autocompleteContext', {
'crudControllerFqcn': ea.crudControllers.findCrudFqcnByEntityFqcn(ea.entity.fqcn),
'propertyName': field.property,
'originatingPage': ea.crud.currentPage,
})
.generateUrl() %}

{% if 'TextType' in field.formType or 'IntegerType' in field.formType %}
{{ form_widget(form_field, {'attr': {'autocomplete': 'off', 'data-lc-autocomplete-url' : url}|raw }) }}
{% else %}
{{ form_widget(form_field) }}
{% endif %}
</div>
{% endif %}
{% endif %}
{% endif %}


</th>
{% endfor %}
<th class="actions">
{{ form_widget(filters_form.action_apply) }}
{% if filters_form_are_not_empty %}
{{ form_widget(filters_form.action_reset) }}
{% else %}
{% do filters_form.action_reset.setRendered %}
{% endif %}
</th>
</tr>
{% endif %}


+ 23
- 2
Resources/views/adminlte/crud/index.html.twig View File

@@ -103,6 +103,11 @@
</th>
</tr>
{% endblock table_head %}

{% block table_filters %}
{{ include('@LcSov/adminlte/block/table_filters.html.twig') }}
{% endblock table_filters %}

</thead>

<tbody>
@@ -177,6 +182,22 @@
{% endblock table_body %}
</tbody>
</table>

{% block filters_form %}
{% if filters_form is defined %}
{{ form_start(filters_form, {'attr' :{'id' : 'filters-form'}}) }}

{#<input type="hidden" name="entity" value="{{ _entity_config.name }}">
<input type="hidden" name="menuIndex" value="{{ app.request.get('menuIndex') }}">
<input type="hidden" name="submenuIndex" value="{{ app.request.get('submenuIndex') }}">
<input type="hidden" name="sortField" value="{{ app.request.get('sortField', '') }}">
<input type="hidden" name="sortDirection"
value="{{ app.request.get('sortDirection', 'DESC') }}">
<input type="hidden" name="action" value="{{ app.request.get('action') }}">#}
{{ form_end(filters_form) }}
{% endif %}
{% endblock filters_form %}

</div>
{% endblock %}
{% block footer %}
@@ -222,7 +243,7 @@
});
});*/

{% if filters|length > 0 %}
/* {% if filters|length > 0 %}
const filterModal = document.querySelector('#modal-filters');

const removeFilter = function (field) {
@@ -283,7 +304,7 @@

{% if has_batch_actions %}

{% endif %}
{% endif %}*/
});
</script>


+ 2
- 2
Translation/TranslatorAdmin.php View File

@@ -39,11 +39,11 @@ class TranslatorAdmin
);
}

public function transChoices($choices, $entityClass): array
public function transChoices(array $choices, string $entityName,string $field): array
{
$newChoices = [];
foreach ($choices as $key => $choice) {
$newChoices[$this->trans($key)] = $choice;
$newChoices[$this->transField($field.'Choices.'.$choice, $entityName)] = $choice;
}

return $newChoices;

Loading…
Cancel
Save