diff --git a/app/Resources/views/base.html.twig b/app/Resources/views/base.html.twig
index e19c635..4473593 100644
--- a/app/Resources/views/base.html.twig
+++ b/app/Resources/views/base.html.twig
@@ -9,8 +9,16 @@
- {{ knp_menu_render('AppBundle:Builder:mainMenu') }}
- {% block body %}{% endblock %}
+ {% block body %}
+
+
+ {% block content %}
+
+ {% endblock %}
+
+ {% endblock %}
+
{% block javascripts %}
diff --git a/app/Resources/views/ldraw/part/index.html.twig b/app/Resources/views/ldraw/part/index.html.twig
new file mode 100644
index 0000000..97c3226
--- /dev/null
+++ b/app/Resources/views/ldraw/part/index.html.twig
@@ -0,0 +1,31 @@
+{% extends 'base.html.twig' %}
+
+{% import 'macro/part.html.twig' as macro %}
+
+{% block content %}
+
+
+
+
+ {% for part in parts %}
+
+ {{ macro.part(part) }}
+
+ {% endfor %}
+
+
+
+ {{ knp_pagination_render(parts) }}
+{% endblock %}
diff --git a/app/Resources/views/ldraw/part/show.html.twig b/app/Resources/views/ldraw/part/show.html.twig
new file mode 100644
index 0000000..ff093f9
--- /dev/null
+++ b/app/Resources/views/ldraw/part/show.html.twig
@@ -0,0 +1,90 @@
+{% extends 'base.html.twig' %}
+
+{% import 'macro/part.html.twig' as macro %}
+
+{% block content %}
+
+ - number:
- {{ part.number }}
+ - name:
- {{ part.name }}
+ - category:
- {{ part.category.name }}
+ - type:
- {{ part.type.name }}
+ - model:
- {{ part.model ? part.model.number }}
+
+
+
+ {% for keyword in part.keywords %}
+ {{ keyword.name }}
+ {% endfor %}
+
+
+
+ {% if apiPart is not null %}
+
+

+
+ {% endif %}
+
+ {% if part is defined and part is not null %}
+
+
+
 }})
+
+
+
alias of
+ {% if part.aliasOf is not null %}
+ {{ macro.part(part.aliasOf) }}
+ {% endif %}
+
+
aliases
+ {% for alias in part.aliases %}
+ {{ macro.part(alias) }}
+ {% endfor %}
+
+
subparts
+ {% for subpart in part.subparts %}
+ {{ macro.part(subpart) }}
+ {% endfor %}
+
+
subpart of
+ {% for subpart in part.subpartOf %}
+ {{ macro.part(subpart) }}
+ {% endfor %}
+
+ {% if part.printOf is not null %}
+
print of
+ {{ macro.part(part.printOf) }}
+ {% endif %}
+
+ {% if part.prints is not empty %}
+
prints
+ {% for print in part.prints %}
+ {{ macro.part(print) }}
+ {% endfor %}
+ {% endif %}
+
+ {% endif %}
+
in sets
+ {% for set in sets %}
+
{{ set.number }}
+ {% endfor %}
+
+
+ {{ dump(rbPart) }}
+ {{ dump(apiPart) }}
+
+{% endblock %}
+
+{% block javascripts %}
+ {{ parent() }}
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/app/Resources/views/macro/part.html.twig b/app/Resources/views/macro/part.html.twig
new file mode 100644
index 0000000..e9cf053
--- /dev/null
+++ b/app/Resources/views/macro/part.html.twig
@@ -0,0 +1,6 @@
+{% macro part(part) %}
+
+{% endmacro %}
\ No newline at end of file
diff --git a/app/Resources/views/part/detail.html.twig b/app/Resources/views/part/detail.html.twig
deleted file mode 100644
index 9fbd654..0000000
--- a/app/Resources/views/part/detail.html.twig
+++ /dev/null
@@ -1,96 +0,0 @@
-{% extends 'base.html.twig' %}
-
-{% block body %}
-
- {{ dump(part) }}
- {{ dump(rbPart) }}
-
- {{ dump(apiPart) }}
-
-
-
- {% if apiPart is not null %}
-
-

-
- {% endif %}
-
- {% if part is not null %}
-
-
-
 }})
-
-
-
alias of
- {% if part.aliasOf is not null %}
-
- {% endif %}
-
-
aliases
- {% for alias in part.aliases %}
-
- {% endfor %}
-
-
subparts
- {% for subpart in part.subparts %}
-
- {% endfor %}
-
-
subpart of
- {% for subpart in part.subpartOf %}
-
- {% endfor %}
-
- {% if part.printOf is not null %}
-
print of
-
- {% endif %}
-
- {% if part.prints is not empty %}
-
prints
- {% for print in part.prints %}
-
- {% endfor %}
- {% endif %}
-
- {% endif %}
-
in sets
- {% for inventory in inventories %}
-
{{ inventory.set.number }}
- {% endfor %}
-
-
-{% endblock %}
-
-{% block javascripts %}
- {{ parent() }}
-
-
-{% endblock %}
\ No newline at end of file
diff --git a/app/Resources/views/part/index.html.twig b/app/Resources/views/part/index.html.twig
deleted file mode 100644
index 007f3ad..0000000
--- a/app/Resources/views/part/index.html.twig
+++ /dev/null
@@ -1,30 +0,0 @@
-{% extends 'base.html.twig' %}
-
-
-{% block body %}
-
- total parts {{ parts.TotalItemCount }}
-
- {{ knp_pagination_render(parts) }}
-
-
-
-
-
- {% for part in parts %}
-
-
 }})
-
-
{{ part.name }}
-
- {% endfor %}
-
-
-
-
- {{ knp_pagination_render(parts) }}
-
-
-
-
-{% endblock %}
\ No newline at end of file
diff --git a/app/config/service/form.yml b/app/config/service/form.yml
new file mode 100644
index 0000000..8dcc806
--- /dev/null
+++ b/app/config/service/form.yml
@@ -0,0 +1,7 @@
+services:
+ form.filter.category:
+ class: AppBundle\Form\Filter\CategoryFilterType
+ arguments:
+ - '@manager.ldraw.category'
+ tags:
+ - { name: form.type }
\ No newline at end of file
diff --git a/app/config/services.yml b/app/config/services.yml
index eba0605..213c66c 100644
--- a/app/config/services.yml
+++ b/app/config/services.yml
@@ -4,3 +4,4 @@ imports:
- { resource: service/loader.yml }
- { resource: service/manager.yml }
- { resource: service/service.yml }
+ - { resource: service/form.yml }
diff --git a/src/AppBundle/Controller/LDraw/PartController.php b/src/AppBundle/Controller/LDraw/PartController.php
new file mode 100644
index 0000000..e794c8a
--- /dev/null
+++ b/src/AppBundle/Controller/LDraw/PartController.php
@@ -0,0 +1,88 @@
+getDoctrine()->getManager();
+
+ $form = $this->get('form.factory')->create(PartFilterType::class);
+
+ $filterBuilder = $this->get('repository.ldraw.part')
+ ->createQueryBuilder('part');
+
+// $filterBuilder->where('part.type = 1');
+
+ if ($request->query->has($form->getName())) {
+ // manually bind values from the request
+ $form->submit($request->query->get($form->getName()));
+
+ // build the query from the given form object
+ $this->get('lexik_form_filter.query_builder_updater')->addFilterConditions($form, $filterBuilder);
+ }
+
+ $paginator = $this->get('knp_paginator');
+ $parts = $paginator->paginate(
+ $filterBuilder->getQuery(),
+ $request->query->getInt('page', 1)/*page number*/,
+ $request->query->getInt('limit', 100)/*limit per page*/
+ );
+
+ return $this->render('ldraw/part/index.html.twig', [
+ 'parts' => $parts,
+ 'form' => $form->createView(),
+ ]);
+ }
+
+ /**
+ * Finds and displays a part entity.
+ *
+ * @Route("/{number}", name="ldraw_part_show")
+ * @Method("GET")
+ */
+ public function showAction($number)
+ {
+ $em = $this->getDoctrine()->getManager();
+
+ $rbPart = $em->getRepository(\AppBundle\Entity\Rebrickable\Part::class)->find($number);
+
+ $part = $em->getRepository(Part::class)->find($number);
+
+ $apiPart = null;
+ try {
+ $apiPart = $this->get('manager.rebrickable')->getPart($number);
+ } catch (\Exception $e) {
+ dump($e);
+ }
+
+ $sets = $em->getRepository(Set::class)->findAllByPartNumber($number);
+
+ return $this->render('ldraw/part/show.html.twig', [
+ 'part' => $part,
+ 'rbPart' => $rbPart,
+ 'apiPart' => $apiPart,
+ 'sets' => $sets,
+ ]);
+ }
+}
diff --git a/src/AppBundle/Controller/PartController.php b/src/AppBundle/Controller/PartController.php
deleted file mode 100644
index c25e8c9..0000000
--- a/src/AppBundle/Controller/PartController.php
+++ /dev/null
@@ -1,80 +0,0 @@
-get('doctrine.orm.default_entity_manager');
-
- $part = $em->getRepository(Part::class)->find($id);
- $rbPart = $em->getRepository(\AppBundle\Entity\Rebrickable\Part::class)->find($id);
-
- $apiPart = null;
- try {
- $apiPart = $this->get('manager.rebrickable')->getPart($id);
- } catch (\Exception $e) {
- dump($e);
- }
-
- $qb = $em->getRepository('AppBundle:Rebrickable\Inventory')->createQueryBuilder('i');
-
- $qb->innerJoin(Inventory_Part::class, 'ip', Join::WITH, 'i.id = ip.inventory')
- ->where('ip.part = :part')
- ->setParameter('part', $id)->distinct(true);
-
- $inventries = $qb->getQuery()->getResult();
-
- return $this->render('part/detail.html.twig', [
- 'part' => $part,
- 'rbPart' => $rbPart,
- 'apiPart' => $apiPart,
- 'inventories' => $inventries,
- ]);
- }
-
- /**
- * @Route("/", name="parts_index")
- */
- public function indexAction(Request $request)
- {
- $em = $this->getDoctrine()->getManager();
-
- $queryBuilder = $em->getRepository(Part::class)->createQueryBuilder('p');
-
- /** @var QueryBuilder $queryBuilder */
-// $queryBuilder->where('p.model is not null');
- $queryBuilder->join(Type::class,'type', JOIN::WITH, 'p.type = type.id')->where( $queryBuilder->expr()->notIn('type.name', ['Alias', 'Obsolete/Subpart']));
-
- $query = $queryBuilder->getQuery();
-
- $paginator = $this->get('knp_paginator');
-
- $parts = $paginator->paginate(
- $query,
- $request->query->getInt('page', 1)/*page number*/,
- $request->query->getInt('limit', 100)/*limit per page*/
- );
-
- return $this->render('part/index.html.twig', [
- 'parts' => $parts,
- ]);
- }
-}
diff --git a/src/AppBundle/Form/Filter/CategoryFilterType.php b/src/AppBundle/Form/Filter/CategoryFilterType.php
new file mode 100644
index 0000000..90f068f
--- /dev/null
+++ b/src/AppBundle/Form/Filter/CategoryFilterType.php
@@ -0,0 +1,49 @@
+categoryManager = $categoryManager;
+ }
+
+ public function buildForm(FormBuilderInterface $builder, array $options)
+ {
+ $builder->add('id', Filters\ChoiceFilterType::class, [
+ 'choices' => $this->categoryManager->findAll(),
+ 'choice_label' => 'name',
+ 'label' => 'filter.part.category',
+ ]);
+ }
+
+ public function getParent()
+ {
+ return Filters\SharedableFilterType::class; // this allow us to use the "add_shared" option
+ }
+
+ public function getBlockPrefix()
+ {
+ return 'category_filter';
+ }
+
+ public function setDefaultOptions(OptionsResolver $resolver)
+ {
+ $resolver->setDefaults([
+ 'data_class' => Category::class,
+ 'csrf_protection' => false,
+ 'validation_groups' => ['filtering'], // avoid NotBlank() constraint-related message
+ 'method' => 'GET',
+ ]);
+ }
+}
diff --git a/src/AppBundle/Form/Filter/PartFilterType.php b/src/AppBundle/Form/Filter/PartFilterType.php
new file mode 100644
index 0000000..ce12c92
--- /dev/null
+++ b/src/AppBundle/Form/Filter/PartFilterType.php
@@ -0,0 +1,58 @@
+add('search', Filters\TextFilterType::class, [
+ 'apply_filter' => [$this, 'partSearchCallback'],
+ 'label' => 'filter.part.search',
+ ]);
+
+ $builder->add('category', CategoryFilterType::class, [
+ 'add_shared' => function (FilterBuilderExecuterInterface $builderExecuter) {
+ $builderExecuter->addOnce($builderExecuter->getAlias().'.category', 'c', function (QueryBuilder $filterBuilder, $alias, $joinAlias, $expr) {
+ $filterBuilder->leftJoin($alias.'.category', $joinAlias);
+ });
+ },
+ ]);
+ }
+
+ public function getBlockPrefix()
+ {
+ return 'part_filter';
+ }
+
+ public function configureOptions(OptionsResolver $resolver)
+ {
+ $resolver->setDefaults([
+ 'csrf_protection' => false,
+ 'validation_groups' => ['filtering'], // avoid NotBlank() constraint-related message
+ ]);
+ }
+
+ public function partSearchCallback(QueryInterface $filterQuery, $field, $values)
+ {
+ if (empty($values['value'])) {
+ return null;
+ }
+
+ // expression that represent the condition
+ $expression = $filterQuery->getExpr()->orX(
+ $filterQuery->getExpr()->like('part.number', ':value'),
+ $filterQuery->getExpr()->like('part.name', ':value')
+ );
+
+ return $filterQuery->createCondition($expression, ['value' => '%'.$values['value'].'%']);
+ }
+}