#365 Add works and activities counter

This commit is contained in:
juminet 2025-04-14 09:34:02 +00:00 committed by Julien Fastré
parent 1c1f418b18
commit 40e373a9c7
9 changed files with 92 additions and 13 deletions

View File

@ -0,0 +1,9 @@
kind: Feature
body: Add counters of actions and activities, with 2 boxes to (1) show the number
of active actions on total actions and (2) show the number of activities in a accompanying
period, and pills in menus for showing the number of active actions and the number
of activities.
time: 2025-03-19T09:00:04.152359515+01:00
custom:
Issue: "365"
SchemaChange: No schema change

View File

@ -11,6 +11,7 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Menu; namespace Chill\ActivityBundle\Menu;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Security\Authorization\ActivityVoter; use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
use Chill\MainBundle\Routing\LocalMenuBuilderInterface; use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod;
@ -23,22 +24,30 @@ use Symfony\Contracts\Translation\TranslatorInterface;
*/ */
class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface
{ {
public function __construct(protected Security $security, protected TranslatorInterface $translator) {} public function __construct(
protected Security $security,
protected TranslatorInterface $translator,
private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry,
) {}
public function buildMenu($menuId, MenuItem $menu, array $parameters) public function buildMenu($menuId, MenuItem $menu, array $parameters)
{ {
$period = $parameters['accompanyingCourse']; $period = $parameters['accompanyingCourse'];
$activities = $this->managerRegistry->getManager()->getRepository(Activity::class)->findBy(
['accompanyingPeriod' => $period]
);
if ( if (
AccompanyingPeriod::STEP_DRAFT !== $period->getStep() AccompanyingPeriod::STEP_DRAFT !== $period->getStep()
&& $this->security->isGranted(ActivityVoter::SEE, $period) && $this->security->isGranted(ActivityVoter::SEE, $period)
) { ) {
$menu->addChild($this->translator->trans('Activity'), [ $menu->addChild($this->translator->trans('Activities'), [
'route' => 'chill_activity_activity_list', 'route' => 'chill_activity_activity_list',
'routeParameters' => [ 'routeParameters' => [
'accompanying_period_id' => $period->getId(), 'accompanying_period_id' => $period->getId(),
], ]) ], ])
->setExtras(['order' => 40]); ->setExtras(['order' => 40, 'counter' => count($activities) > 0 ? count($activities) : null]);
} }
} }

View File

@ -11,6 +11,7 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Menu; namespace Chill\ActivityBundle\Menu;
use Chill\ActivityBundle\Repository\ActivityACLAwareRepositoryInterface;
use Chill\ActivityBundle\Security\Authorization\ActivityVoter; use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
use Chill\MainBundle\Routing\LocalMenuBuilderInterface; use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
@ -23,13 +24,20 @@ use Symfony\Contracts\Translation\TranslatorInterface;
*/ */
final readonly class PersonMenuBuilder implements LocalMenuBuilderInterface final readonly class PersonMenuBuilder implements LocalMenuBuilderInterface
{ {
public function __construct(private AuthorizationCheckerInterface $authorizationChecker, private TranslatorInterface $translator) {} public function __construct(
private readonly ActivityACLAwareRepositoryInterface $activityACLAwareRepository,
private AuthorizationCheckerInterface $authorizationChecker,
private TranslatorInterface $translator,
) {}
public function buildMenu($menuId, MenuItem $menu, array $parameters) public function buildMenu($menuId, MenuItem $menu, array $parameters)
{ {
/** @var Person $person */ /** @var Person $person */
$person = $parameters['person']; $person = $parameters['person'];
$count = $this->activityACLAwareRepository->countByPerson($person, ActivityVoter::SEE);
if ($this->authorizationChecker->isGranted(ActivityVoter::SEE, $person)) { if ($this->authorizationChecker->isGranted(ActivityVoter::SEE, $person)) {
$menu->addChild( $menu->addChild(
$this->translator->trans('Activities'), $this->translator->trans('Activities'),
@ -38,7 +46,7 @@ final readonly class PersonMenuBuilder implements LocalMenuBuilderInterface
'routeParameters' => ['person_id' => $person->getId()], 'routeParameters' => ['person_id' => $person->getId()],
] ]
) )
->setExtra('order', 201); ->setExtras(['order' => 201, 'counter' => $count > 0 ? $count : null]);
} }
} }

View File

@ -204,20 +204,25 @@ final class AccompanyingCourseController extends \Symfony\Bundle\FrameworkBundle
['date' => 'DESC', 'id' => 'DESC'], ['date' => 'DESC', 'id' => 'DESC'],
); );
$activities = \array_slice($activities, 0, 3);
$works = $this->workRepository->findByAccompanyingPeriod( $works = $this->workRepository->findByAccompanyingPeriod(
$accompanyingCourse, $accompanyingCourse,
['startDate' => 'DESC', 'endDate' => 'DESC'], ['startDate' => 'DESC', 'endDate' => 'DESC'],
3 3
); );
$counters = [
'activities' => count($activities),
'openWorks' => count($accompanyingCourse->getOpenWorks()),
'works' => count($works),
];
return $this->render('@ChillPerson/AccompanyingCourse/index.html.twig', [ return $this->render('@ChillPerson/AccompanyingCourse/index.html.twig', [
'accompanyingCourse' => $accompanyingCourse, 'accompanyingCourse' => $accompanyingCourse,
'withoutHousehold' => $withoutHousehold, 'withoutHousehold' => $withoutHousehold,
'participationsByHousehold' => $accompanyingCourse->actualParticipationsByHousehold(), 'participationsByHousehold' => $accompanyingCourse->actualParticipationsByHousehold(),
'works' => $works, 'works' => $works,
'activities' => $activities, 'activities' => \array_slice($activities, 0, 3),
'counters' => $counters,
]); ]);
} }

View File

@ -511,6 +511,14 @@ class AccompanyingPeriod implements
return $this->getParticipationsContainsPerson($person)->count() > 0; return $this->getParticipationsContainsPerson($person)->count() > 0;
} }
public function getOpenWorks(): Collection
{
return $this->getWorks()->filter(
static fn (AccompanyingPeriodWork $work): bool => null === $work->getEndDate()
or $work->getEndDate() > new \DateTimeImmutable('today')
);
}
/** /**
* Open a new participation for a person. * Open a new participation for a person.
*/ */

View File

@ -71,7 +71,7 @@ class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface
->setExtras(['order' => 30]); ->setExtras(['order' => 30]);
*/ */
$menu->addChild($this->translator->trans('Accompanying Course Comment'), [ $menu->addChild($this->translator->trans('Accompanying Course Comments'), [
'route' => 'chill_person_accompanying_period_comment_list', 'route' => 'chill_person_accompanying_period_comment_list',
'routeParameters' => [ 'routeParameters' => [
'accompanying_period_id' => $period->getId(), 'accompanying_period_id' => $period->getId(),
@ -80,12 +80,15 @@ class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface
} }
if ($this->security->isGranted(AccompanyingPeriodWorkVoter::SEE, $period)) { if ($this->security->isGranted(AccompanyingPeriodWorkVoter::SEE, $period)) {
$menu->addChild($this->translator->trans('Accompanying Course Action'), [ $menu->addChild($this->translator->trans('Accompanying Course Actions'), [
'route' => 'chill_person_accompanying_period_work_list', 'route' => 'chill_person_accompanying_period_work_list',
'routeParameters' => [ 'routeParameters' => [
'id' => $period->getId(), 'id' => $period->getId(),
], ]) ], ])
->setExtras(['order' => 40]); ->setExtras([
'order' => 40,
'counter' => count($period->getOpenWorks()) > 0 ? count($period->getOpenWorks()) : null,
]);
} }
$workflow = $this->registry->get($period, 'accompanying_period_lifecycle'); $workflow = $this->registry->get($period, 'accompanying_period_lifecycle');

View File

@ -304,5 +304,14 @@ div#dashboards {
margin: 0; margin: 0;
} }
} }
div.count-item {
font-size: 3rem;
text-align: center;
}
div.count-item-label {
font-size: 90%;
font-variant: all-small-caps;
text-align: center;
}
} }
} }

View File

@ -201,7 +201,7 @@
{% endif %} {% endif %}
{% if accompanyingCourse.step != 'DRAFT' %} {% if accompanyingCourse.step != 'DRAFT' %}
<div class="mbloc col col-sm-6 col-lg-4"> <div class="mbloc col col-sm-6 col-lg-8 col-xxl-4">
<div class="notification-counter"> <div class="notification-counter">
<h4 class="item-key">{{ 'notification.Notifications'|trans }}</h4> <h4 class="item-key">{{ 'notification.Notifications'|trans }}</h4>
{% set notif_counter = chill_count_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', accompanyingCourse.id) %} {% set notif_counter = chill_count_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', accompanyingCourse.id) %}
@ -238,6 +238,31 @@
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% if counters.activities > 0 %}
<div class="mbloc col col-sm-6 col-lg-4">
<div class="count-activities">
<div class="count-item">{{ counters.activities }}</div>
<div class="count-item-label">
{% if counters.activities == 1 %}
{{ 'Activity'|trans }}
{% else %}
{{ 'Activities'|trans }}
{% endif %}
</div>
</div>
</div>
{% endif %}
{% if counters.works > 0 %}
<div class="mbloc col col-sm-6 col-lg-4">
<div class="count-works">
<div class="count-item">{{ counters.openWorks }} / {{ counters.works }}</div>
<div class="count-item-label">{{ 'accompanying_course_work.On-going works over total'|trans }}</div>
</div>
</div>
{% endif %}
</div> </div>
<div class="social-actions my-4"> <div class="social-actions my-4">

View File

@ -804,7 +804,7 @@ person_admin:
# specific to accompanying period # specific to accompanying period
accompanying_period: accompanying_period:
deleted: Parcours d'accompagnment supprimé deleted: Parcours d'accompagnement supprimé
dates: Période dates: Période
dates_from_%opening_date%: Ouvert depuis le %opening_date% dates_from_%opening_date%: Ouvert depuis le %opening_date%
dates_from_%opening_date%_to_%closing_date%: Ouvert du %opening_date% au %closing_date% dates_from_%opening_date%_to_%closing_date%: Ouvert du %opening_date% au %closing_date%
@ -843,6 +843,7 @@ accompanying_course:
administrative_location: Localisation administrative administrative_location: Localisation administrative
comment is pinned: Le commentaire est épinglé comment is pinned: Le commentaire est épinglé
comment is unpinned: Le commentaire est désépinglé comment is unpinned: Le commentaire est désépinglé
show: Montrer show: Montrer
hide: Masquer hide: Masquer
closed periods: parcours clôturés closed periods: parcours clôturés
@ -851,6 +852,7 @@ Social work configuration: Gestion des actions d'accompagnement social
# Accompanying Course comments # Accompanying Course comments
Accompanying Course Comment: Commentaire Accompanying Course Comment: Commentaire
Accompanying Course Comments: Commentaires
Accompanying Course Comment list: Commentaires du parcours Accompanying Course Comment list: Commentaires du parcours
pinned: épinglé pinned: épinglé
Pin comment: Épingler Pin comment: Épingler
@ -919,6 +921,7 @@ accompanying_course_work:
date_filter: Filtrer par date date_filter: Filtrer par date
types_filter: Filtrer par type d'action types_filter: Filtrer par type d'action
user_filter: Filtrer par intervenant user_filter: Filtrer par intervenant
On-going works over total: Actions en cours / Actions du parcours
# #