diff --git a/assets/controllers/customers_controller.js b/assets/controllers/customers_controller.js index 45829e3c..f6de2781 100644 --- a/assets/controllers/customers_controller.js +++ b/assets/controllers/customers_controller.js @@ -63,6 +63,12 @@ export default class extends Controller { url, method: 'GET', target, + onSuccess: (html) => { + target.innerHTML = html; + // rAF stellt sicher, dass Bootstrap das neue DOM + // registriert hat, bevor Listener angehängt werden + requestAnimationFrame(() => initReservationHistoryPagination()); + } }); } @@ -89,3 +95,69 @@ export default class extends Controller { }); } } + +function initReservationHistoryPagination() { + const perPage = 10; + const tbody = document.querySelector('#reservationHistoryCollapse tbody'); + if (!tbody) return; + const rows = Array.from(tbody.querySelectorAll('tr')); + + // Alten Pager aus vorherigem Modal-Aufruf entfernen + const oldPager = document.getElementById('res-history-pager'); + if (oldPager) oldPager.remove(); + + if (rows.length <= perPage) return; + + let currentPage = 1; + const pages = Math.ceil(rows.length / perPage); + + function showPage(page) { + currentPage = page; + rows.forEach((row, i) => { + row.style.display = (i >= (page-1)*perPage && i < page*perPage) ? '' : 'none'; + }); + renderPager(); + } + + function renderPager() { + let pager = document.getElementById('res-history-pager'); + if (!pager) { + pager = document.createElement('div'); + pager.id = 'res-history-pager'; + pager.className = 'px-3 py-2'; + document.querySelector('#reservationHistoryCollapse .accordion-body').appendChild(pager); + } + pager.innerHTML = ''; + const ul = document.createElement('ul'); + ul.className = 'pagination pagination-sm mb-0'; + + const prev = document.createElement('li'); + prev.className = 'page-item' + (currentPage === 1 ? ' disabled' : ''); + prev.innerHTML = '«'; + if (currentPage > 1) prev.querySelector('a').addEventListener('click', e => { e.preventDefault(); showPage(currentPage-1); }); + ul.appendChild(prev); + + for (let i = 1; i <= pages; i++) { + const li = document.createElement('li'); + li.className = 'page-item' + (i === currentPage ? ' active' : ''); + const p = i; + li.innerHTML = '' + i + ''; + li.querySelector('a').addEventListener('click', e => { e.preventDefault(); showPage(p); }); + ul.appendChild(li); + } + + const next = document.createElement('li'); + next.className = 'page-item' + (currentPage === pages ? ' disabled' : ''); + next.innerHTML = '»'; + if (currentPage < pages) next.querySelector('a').addEventListener('click', e => { e.preventDefault(); showPage(currentPage+1); }); + ul.appendChild(next); + pager.appendChild(ul); + } + + const collapse = document.getElementById('reservationHistoryCollapse'); + if (collapse) { + collapse.addEventListener('shown.bs.collapse', () => showPage(1), { once: true }); + if (collapse.classList.contains('show')) showPage(1); + } +} + diff --git a/src/Controller/CustomerServiceController.php b/src/Controller/CustomerServiceController.php index 51343df0..0a53a53b 100644 --- a/src/Controller/CustomerServiceController.php +++ b/src/Controller/CustomerServiceController.php @@ -17,6 +17,7 @@ use App\Entity\CustomerAddresses; use App\Entity\Enum\IDCardType; use App\Entity\Template; +use App\Repository\ReservationRepository; use App\Service\CSRFProtectionService; use App\Service\CustomerService; use App\Service\TemplatesService; @@ -100,14 +101,23 @@ public function searchCustomersAction(ManagerRegistry $doctrine, Request $reques } #[Route('/{id}/get', name: 'customers.get.customer', methods: ['GET'], defaults: ['id' => '0'])] - public function getCustomerAction(ManagerRegistry $doctrine, CSRFProtectionService $csrf, $id) - { + public function getCustomerAction( + ManagerRegistry $doctrine, + CSRFProtectionService $csrf, + ReservationRepository $reservationRepo, + $id + ) { $em = $doctrine->getManager(); $customer = $em->getRepository(Customer::class)->find($id); + $reservations = $customer + ? $reservationRepo->loadAllReservationsForCustomer($customer) + : []; + return $this->render('Customers/customer_form_show.html.twig', [ - 'customer' => $customer, - 'token' => $csrf->getCSRFTokenForForm(), + 'customer' => $customer, + 'token' => $csrf->getCSRFTokenForForm(), + 'reservations' => $reservations, ]); } diff --git a/src/Repository/ReservationRepository.php b/src/Repository/ReservationRepository.php index 2674e56f..300058b3 100644 --- a/src/Repository/ReservationRepository.php +++ b/src/Repository/ReservationRepository.php @@ -513,4 +513,26 @@ public function findImportedWithoutBookerPaginated(int $page, int $perPage): arr ->getQuery() ->getResult(); } + + /** + * Alle Reservierungen eines Gastes – als Buchender (booker) + * oder als Mitreisender – dedupliziert, nach Anreise absteigend. + * + * @return Reservation[] + */ + public function loadAllReservationsForCustomer(\App\Entity\Customer $customer): array + { + return $this->createQueryBuilder('r') + ->select('r', 'a', 'rs', 'i') + ->leftJoin('r.appartment', 'a') + ->leftJoin('r.reservationStatus', 'rs') + ->leftJoin('r.invoices', 'i') + ->where('r.booker = :customer') + ->orWhere(':customer MEMBER OF r.customers') + ->setParameter('customer', $customer) + ->orderBy('r.startDate', 'DESC') + ->distinct() + ->getQuery() + ->getResult(); + } } diff --git a/templates/Customers/customer_form_show.html.twig b/templates/Customers/customer_form_show.html.twig index 5f00eb5c..c1f36ecd 100644 --- a/templates/Customers/customer_form_show.html.twig +++ b/templates/Customers/customer_form_show.html.twig @@ -12,13 +12,15 @@ - -