feat: paginate the admin booking list
This commit is contained in:
1 parent
ba64033948
commit
2f5d1d14e4
16 files changed
+504
-75
No files matched your search
@@ -9,6 +9,7 @@ use App\Form\Admin\BookingFilterType;
|
||||
use App\Repository\BookingRepository;
|
||||
use App\Repository\BookingSeriesRepository;
|
||||
use App\Security\Role;
|
||||
use Knp\Component\Pager\PaginatorInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
@@ -30,9 +31,16 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
#[IsGranted(Role::ADMIN)]
|
||||
final class IndexController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* The window defaults to a year, so the unpaged list grew with the company. 25 rows is about
|
||||
* one screen of the table without scrolling past the filter form.
|
||||
*/
|
||||
private const PER_PAGE = 25;
|
||||
|
||||
public function __construct(
|
||||
private readonly BookingRepository $bookings,
|
||||
private readonly BookingSeriesRepository $series,
|
||||
private readonly PaginatorInterface $paginator,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -45,10 +53,19 @@ final class IndexController extends AbstractController
|
||||
// Unfiltered first visits simply keep the constructor's defaults.
|
||||
$form->handleRequest($request);
|
||||
|
||||
// No defaultSortFieldName: one field cannot express the date/start/resource ordering the
|
||||
// repository already sorts by, and naming one here would replace all three. Unsorted
|
||||
// requests keep that ordering; a clicked column header takes precedence over it.
|
||||
$pagination = $this->paginator->paginate(
|
||||
$this->bookings->createFilteredQueryBuilder($filter),
|
||||
$request->query->getInt('page', 1),
|
||||
self::PER_PAGE,
|
||||
);
|
||||
|
||||
return $this->render('admin/bookings.html.twig', [
|
||||
'form' => $form,
|
||||
'filter' => $filter,
|
||||
'bookings' => $this->bookings->findForAdmin($filter),
|
||||
'pagination' => $pagination,
|
||||
'series' => $this->series->findActive($filter->fromDate(), $filter->user, $filter->type),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -61,6 +61,10 @@ final class BookingFilterType extends AbstractType
|
||||
'data_class' => BookingFilter::class,
|
||||
'method' => 'GET',
|
||||
'csrf_protection' => false,
|
||||
// The paginator writes page/sort/direction into the same query string this form reads
|
||||
// back. Without this the form counts them as extra fields, turns invalid, and the
|
||||
// listing answers 422 to every sorted request.
|
||||
'allow_extra_fields' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -216,9 +216,11 @@ class BookingRepository extends ServiceEntityRepository
|
||||
* and eager-selects the holder as well, because the admin list names a different person on
|
||||
* every row and would otherwise fire a query per booking.
|
||||
*
|
||||
* @return list<Booking>
|
||||
* Unexecuted, because the paginator has to add the LIMIT and the ORDER BY of a clicked column
|
||||
* header itself. Only to-one associations are fetch-joined, so a LIMIT over this query cannot
|
||||
* truncate a half-hydrated row the way a joined collection would.
|
||||
*/
|
||||
public function findForAdmin(BookingFilter $filter): array
|
||||
public function createFilteredQueryBuilder(BookingFilter $filter): QueryBuilder
|
||||
{
|
||||
$qb = $this->createQueryBuilder('b')
|
||||
->addSelect('u', 'r', 'g', 's')
|
||||
@@ -241,7 +243,7 @@ class BookingRepository extends ServiceEntityRepository
|
||||
$qb->andWhere('r.type = :type')->setParameter('type', $filter->type);
|
||||
}
|
||||
|
||||
return $qb->getQuery()->getResult();
|
||||
return $qb;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in new issue
Block a user