Add ticket listing and related enhancements

Added a new functionality for listing tickets with the ability for the user to order the list. A method was added to the User class to identify if an object is an instance of User. Similarly, a method was added to the UserGroup class. User.php, UserGroup.php, TicketRepository.php, and TicketRepositoryInterface.php were updated. A new TicketListController, MotiveRepository, and SectionMenuBuilder were created. Translations were included, and services.yaml was updated.
This commit is contained in:
Julien Fastré 2024-05-31 12:32:01 +02:00
parent 50025044d3
commit 26dfa9b028
Signed by: julienfastre
GPG Key ID: BDE2190974723FCB
10 changed files with 285 additions and 1 deletions

View File

@ -608,4 +608,14 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter
return $this;
}
/**
* Check if the current object is an instance of User.
*
* @return bool returns true if the current object is an instance of User, false otherwise
*/
public function isUser(): bool
{
return true;
}
}

View File

@ -136,4 +136,16 @@ class UserGroup
return $this;
}
/**
* Checks if the current object is an instance of the UserGroup class.
*
* In use in twig template, to discriminate when there an object can be polymorphic.
*
* @return bool returns true if the current object is an instance of UserGroup, false otherwise
*/
public function isUserGroup(): bool
{
return true;
}
}

View File

@ -0,0 +1,84 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\TicketBundle\Controller;
use Chill\MainBundle\Templating\Listing\FilterOrderHelper;
use Chill\MainBundle\Templating\Listing\FilterOrderHelperFactory;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\TicketBundle\Entity\Motive;
use Chill\TicketBundle\Repository\MotiveRepository;
use Chill\TicketBundle\Repository\TicketRepositoryInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Security;
use Twig\Environment;
use Twig\Error\LoaderError;
use Twig\Error\RuntimeError;
use Twig\Error\SyntaxError;
final readonly class TicketListController
{
public function __construct(
private Security $security,
private TicketRepositoryInterface $ticketRepository,
private Environment $twig,
private FilterOrderHelperFactory $filterOrderHelperFactory,
// private MotiveRepository $motiveRepository,
// private TranslatableStringHelperInterface $translatableStringHelper,
) {}
/**
* @throws RuntimeError
* @throws SyntaxError
* @throws LoaderError
*/
#[Route('/{_locale}/ticket/ticket/list', name: 'chill_ticket_ticket_list')]
public function __invoke(Request $request): Response
{
if (!$this->security->isGranted('ROLE_USER')) {
throw new AccessDeniedHttpException('only user can access this page');
}
$filter = $this->buildFilter();
$tickets = $this->ticketRepository->findAllOrdered();
return new Response(
$this->twig->render('@ChillTicket/Ticket/list.html.twig', [
'tickets' => $tickets,
'filter' => $filter,
])
);
}
private function buildFilter(): FilterOrderHelper
{
// $motives = $this->motiveRepository->findAll();
return $this->filterOrderHelperFactory
->create(__CLASS__)
->addSingleCheckbox('to_me', 'chill_ticket.list.filter.to_me')
->addSingleCheckbox('in_alert', 'chill_ticket.list.filter.in_alert')
->addDateRange('created_between', 'chill_ticket.list.filter.created_between')
/*
->addEntityChoice('by_motive', 'chill_ticket.list.filter.by_motive', Motive::class, $motives, [
'choice_label' => fn (Motive $motive) => $this->translatableStringHelper->localize($motive->getLabel()),
'expanded' => true,
'multiple' => true,
'attr' => ['class' => 'select2'],
])
*/
->build();
}
}

View File

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\TicketBundle\Menu;
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
use Knp\Menu\MenuItem;
class SectionMenuBuilder implements LocalMenuBuilderInterface
{
public function buildMenu($menuId, MenuItem $menu, array $parameters)
{
$menu->addChild('Tickets', ['route' => 'chill_ticket_ticket_list'])
->setExtras(['order' => 250]);
}
public static function getMenuIds(): array
{
return ['section'];
}
}

View File

@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\TicketBundle\Repository;
use Chill\TicketBundle\Entity\Motive;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @template-extends ServiceEntityRepository<Motive>
*/
class MotiveRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Motive::class);
}
}

View File

@ -19,7 +19,7 @@ final readonly class TicketRepository implements TicketRepositoryInterface
{
private ObjectRepository $repository;
public function __construct(EntityManagerInterface $objectManager)
public function __construct(private EntityManagerInterface $objectManager)
{
$this->repository = $objectManager->getRepository($this->getClassName());
}
@ -34,6 +34,14 @@ final readonly class TicketRepository implements TicketRepositoryInterface
return $this->repository->findAll();
}
/**
* @return list<Ticket>
*/
public function findAllOrdered(): array
{
return $this->objectManager->createQuery('SELECT t FROM '.$this->getClassName().' t ORDER BY t.id DESC')->getResult();
}
public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array
{
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);

View File

@ -20,4 +20,9 @@ use Doctrine\Persistence\ObjectRepository;
interface TicketRepositoryInterface extends ObjectRepository
{
public function findOneByExternalRef(string $extId): ?Ticket;
/**
* @return list<Ticket>
*/
public function findAllOrdered(): array;
}

View File

@ -0,0 +1,97 @@
{% extends '@ChillMain/layout.html.twig' %}
{% block title 'chill_ticket.list.title'|trans %}
{% block content %}
<h1>{{ block('title') }}</h1>
{{ filter|chill_render_filter_order_helper }}
{% if tickets|length == 0 %}
<p class="chill-no-data-statement">{{ chill_ticket.list.no_tickets }}</p>
{% else %}
<div class="flex-table">
{% for ticket in tickets %}
<div class="item-bloc">
<div class="item-row">
<div class="wrap-header">
<div class="wh-row">
<div class="wh-col">
{% if ticket.motive is not null %}
<span class="h2" style="color: var(--bs-chill-blue); font-variant: all-small-caps">
{{ ticket.motive.label|localize_translatable_string }}
</span>
{% else %}
<span class="h3" style="color: var(--bs-chill-blue); font-style: italic">Sans motif</span>
{% endif %}
</div>
<div class="wh-col">
<p style="font-size: 1.5rem;"><span class="badge text-bg-chill-green text-white">Ouvert</span></p>
</div>
</div>
<div class="wh-row">
<div class="wh-col">
#{{ ticket.id }}
</div>
<div class="wh-col">
{% if ticket.createdAt is not null %}
<span title="{{ ticket.createdAt|format_datetime('long', 'long') }}" style="font-style: italic;">{{ ticket.createdAt|ago|capitalize }}</span>
{% endif %}
</div>
</div>
</div>
</div>
{% if ticket.persons|length > 0 or ticket.currentAddressee|length > 0 %}
<div class="item-row separator">
<div class="wrap-list">
{% if ticket.persons|length > 0 %}
<div class="wl-row">
<div class="wl-col title"><h3 class="mb-2">Patient concerné</h3></div>
<div class="wl-col list">
{% for p in ticket.persons %}
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
targetEntity: { name: 'person', id: p.id },
action: 'show',
displayBadge: true,
buttonText: p|chill_entity_render_string,
isDead: p.deathdate is not null
} %}
{% endfor %}
</div>
</div>
{% endif %}
{% if ticket.currentAddressee|length > 0 %}
<div class="wl-row">
<div class="wl-col title"><h3 class="mb-2">Destinataires</h3></div>
<div class="wl-col list">
{% for d in ticket.currentAddressee %}
{% if d.isUser is defined and d.isUser %}
{{ d|chill_entity_render_box }}
{% elseif d.isUserGroup is defined and d.isUserGroup %}
{{ d|chill_entity_render_box }}
{% endif %}
{% endfor %}
</div>
</div>
{% endif %}
</div>
</div>
{% endif %}
<div class="item-row separator">
<ul class="record_actions">
<li>
<a class="btn btn-update" href="{{ chill_path_add_return_path('chill_ticket_ticket_edit', {'id': ticket.id}) }}"></a>
</li>
</ul>
</div>
</div>
{% endfor %}
</div>
{% endif %}
<ul class="record_actions sticky-form-buttons">
<li>
<a href="{{ chill_path_add_return_path('chill_ticket_createticket__invoke') }}" class="btn btn-create">{{ 'Create'|trans }}</a>
</li>
</ul>
{% endblock %}

View File

@ -17,5 +17,8 @@ services:
Chill\TicketBundle\Serializer\:
resource: '../Serializer/'
Chill\TicketBundle\Menu\:
resource: '../Menu/'
Chill\TicketBundle\DataFixtures\:
resource: '../DataFixtures/'

View File

@ -0,0 +1,9 @@
chill_ticket:
list:
title: Tickets
filter:
to_me: Tickets qui me sont attribués
in_alert: Tickets en alerte (délai de résolution dépassé)
created_between: Créés entre