Compare commits

...

2 Commits

Author SHA1 Message Date
635efd6f1d Update calendar with accepted invites 2025-11-12 17:01:09 +01:00
869880d8f3 Revert "Display calendar items linked to person within search results"
This reverts commit f7ea7e4dbf.
2025-11-12 13:08:54 +01:00
7 changed files with 93 additions and 80 deletions

View File

@@ -1,6 +0,0 @@
kind: Feature
body: Display appointments (calendar items) linked to a person within the search results, like it was done for accompanying periods
time: 2025-11-12T13:00:16.844313583+01:00
custom:
Issue: "462"
SchemaChange: No schema change

View File

@@ -0,0 +1,6 @@
kind: Fixed
body: Display calendar items for which an invite was accepted on the mes rendez-vous page
time: 2025-11-12T17:00:25.58946528+01:00
custom:
Issue: "463"
SchemaChange: No schema change

View File

@@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\CalendarBundle\Controller;
use Chill\CalendarBundle\Repository\CalendarRepository;
use Chill\CalendarBundle\Repository\InviteRepository;
use Chill\MainBundle\CRUD\Controller\ApiController;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Serializer\Model\Collection;
@@ -23,7 +24,10 @@ use Symfony\Component\Routing\Annotation\Route;
class CalendarAPIController extends ApiController
{
public function __construct(private readonly CalendarRepository $calendarRepository) {}
public function __construct(
private readonly CalendarRepository $calendarRepository,
private readonly InviteRepository $inviteRepository,
) {}
#[Route(path: '/api/1.0/calendar/calendar/by-user/{id}.{_format}', name: 'chill_api_single_calendar_list_by-user', requirements: ['_format' => 'json'])]
public function listByUser(User $user, Request $request, string $_format): JsonResponse
@@ -52,16 +56,37 @@ class CalendarAPIController extends ApiController
throw new BadRequestHttpException('dateTo not parsable');
}
$total = $this->calendarRepository->countByUser($user, $dateFrom, $dateTo);
$paginator = $this->getPaginatorFactory()->create($total);
$ranges = $this->calendarRepository->findByUser(
// Get calendar items where user is the main user
$ownCalendars = $this->calendarRepository->findByUser(
$user,
$dateFrom,
$dateTo,
$paginator->getItemsPerPage(),
$paginator->getCurrentPageFirstItemNumber()
$dateTo
);
// Get calendar items from accepted invites
$acceptedInvites = $this->inviteRepository->findAcceptedInvitesByUserAndDateRange($user, $dateFrom, $dateTo);
$inviteCalendars = array_map(fn ($invite) => $invite->getCalendar(), $acceptedInvites);
// Merge
$allCalendars = array_merge($ownCalendars, $inviteCalendars);
$uniqueCalendars = [];
$seenIds = [];
foreach ($allCalendars as $calendar) {
$id = $calendar->getId();
if (!in_array($id, $seenIds, true)) {
$seenIds[] = $id;
$uniqueCalendars[] = $calendar;
}
}
$total = count($uniqueCalendars);
$paginator = $this->getPaginatorFactory()->create($total);
$offset = $paginator->getCurrentPageFirstItemNumber();
$limit = $paginator->getItemsPerPage();
$ranges = array_slice($uniqueCalendars, $offset, $limit);
$collection = new Collection($ranges, $paginator);
return $this->json($collection, Response::HTTP_OK, [], ['groups' => ['calendar:light']]);

View File

@@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\CalendarBundle\Repository;
use Chill\CalendarBundle\Entity\Invite;
use Chill\MainBundle\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\Persistence\ObjectRepository;
@@ -51,6 +52,52 @@ class InviteRepository implements ObjectRepository
return $this->entityRepository->findOneBy($criteria);
}
/**
* Find accepted invites for a user within a date range.
*
* @return array|Invite[]
*/
public function findAcceptedInvitesByUserAndDateRange(User $user, \DateTimeImmutable $from, \DateTimeImmutable $to): array
{
return $this->buildAcceptedInviteByUserAndDateRangeQuery($user, $from, $to)
->getQuery()
->getResult();
}
/**
* Count accepted invites for a user within a date range.
*/
public function countAcceptedInvitesByUserAndDateRange(User $user, \DateTimeImmutable $from, \DateTimeImmutable $to): int
{
return $this->buildAcceptedInviteByUserAndDateRangeQuery($user, $from, $to)
->select('COUNT(c)')
->getQuery()
->getSingleScalarResult();
}
public function buildAcceptedInviteByUserAndDateRangeQuery(User $user, \DateTimeImmutable $from, \DateTimeImmutable $to)
{
$qb = $this->entityRepository->createQueryBuilder('i');
return $qb
->join('i.calendar', 'c')
->where(
$qb->expr()->andX(
$qb->expr()->eq('i.user', ':user'),
$qb->expr()->eq('i.status', ':status'),
$qb->expr()->gte('c.startDate', ':startDate'),
$qb->expr()->lte('c.endDate', ':endDate'),
$qb->expr()->isNull('c.cancelReason')
)
)
->setParameters([
'user' => $user,
'status' => Invite::ACCEPTED,
'startDate' => $from,
'endDate' => $to,
]);
}
public function getClassName(): string
{
return Invite::class;

View File

@@ -14,13 +14,13 @@ div.calendar-list {
ul.calendar-list {
display: inline-block;
}
}
.calendar-list__global {
display: inline-block;
padding: 0.2rem;
min-width: 2rem;
border: 1px solid var(--bs-chill-blue);
border-radius: 0.25rem;
text-align: center;
& > a.calendar-list__global {
display: inline-block;
padding: 0.2rem;
min-width: 2rem;
border: 1px solid var(--bs-chill-blue);
border-radius: 0.25rem;
text-align: center;
}
}

View File

@@ -866,29 +866,6 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
return $this->calendars;
}
/**
* Get next calendars for this person.
* Only returns calendars where this person is in the persons collection and the person property is not null.
*
* @param int $limit Maximum number of calendars to return
*
* @return ReadableCollection<int, Calendar>
*/
public function getNextCalendarsForPerson(int $limit = 5): ReadableCollection
{
$today = new \DateTimeImmutable('today');
$filtered = $this->calendars->filter(
fn (Calendar $calendar) => $calendar->getStartDate() >= $today
&& $calendar->getPerson() === $this
);
$sorted = $filtered->toArray();
usort($sorted, fn ($a, $b) => $a->getStartDate() <=> $b->getStartDate());
return new ArrayCollection(array_slice($sorted, 0, $limit));
}
public function getCenter(): ?Center
{
if (null !== $this->centerCurrent) {

View File

@@ -189,43 +189,7 @@
{% endfor %}
</li>
{% endif %}
{% set calendars = [] %}
{% for c in person.getNextCalendarsForPerson(10) %}
{% if is_granted('CHILL_CALENDAR_CALENDAR_SEE', c) %}
{% set calendars = calendars|merge([c]) %}
{% endif %}
{% endfor %}
{% if calendars|length > 0 %}
<div class="d-flex align-items-center justify-content-between flex-wrap gap-3 mt-3">
<h5 class="mb-0">{{ 'chill_calendar.Next calendars'|trans }}</h5>
<ul class="list-inline mb-0 d-flex flex-wrap align-items-center">
{% for c in calendars %}
<li class="list-inline-item">
{% if is_granted('CHILL_CALENDAR_CALENDAR_EDIT', c) %}
<a href="{{ chill_path_add_return_path('chill_calendar_calendar_edit', { id: c.id }) }}">
<span class="badge bg-secondary">
{{ c.startDate|format_datetime('long', 'short') }}
</span>
</a>
{% else %}
<span class="badge bg-secondary">
{{ c.startDate|format_datetime('long', 'short') }}
</span>
{% endif %}
</li>
{% endfor %}
</ul>
{% if is_granted('CHILL_CALENDAR_CALENDAR_SEE', person) %}
<a href="{{ chill_path_add_return_path('chill_calendar_calendar_list_by_person', {'id': person.id}) }}" class="calendar-list__global"><i class="fa fa-list"></i></a>
{% endif %}
</div>
{% endif %}
</ul>
</div>
<div class="item-col">
<ul class="record_actions">
{% if options['customButtons']['before'] is defined %}
{{ options['customButtons']['before'] }}