From 8228c0d15371058e7b471125a09362d544a16a55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bjo=CC=88rn=20Fromme?= Date: Mon, 25 Oct 2021 14:41:27 +0200 Subject: [PATCH] Migrate mobile search --- .../Controller/AjaxFilterPanelController.php | 1 + .../Controller/AjaxSearchController.php | 8 +- .../Classes/Controller/SearchController.php | 8 +- .../Classes/Domain/Model/FilterOptions.php | 27 +++- .../Domain/Repository/ProductRepository.php | 11 +- .../Classes/Service/FilterService.php | 130 +++++++++++++++++- .../Classes/Service/SearchResultService.php | 11 ++ .../controllers/mobile_search_controller.js | 12 +- .../Private/Partials/Search/FilterPanel.html | 21 ++- .../Private/Partials/Search/Main.html | 33 +---- .../Private/Partials/Search/MobileSearch.html | 6 +- 11 files changed, 198 insertions(+), 70 deletions(-) diff --git a/public/typo3conf/ext/ep_products/Classes/Controller/AjaxFilterPanelController.php b/public/typo3conf/ext/ep_products/Classes/Controller/AjaxFilterPanelController.php index e4b79f7a..294a804c 100644 --- a/public/typo3conf/ext/ep_products/Classes/Controller/AjaxFilterPanelController.php +++ b/public/typo3conf/ext/ep_products/Classes/Controller/AjaxFilterPanelController.php @@ -75,6 +75,7 @@ class AjaxFilterPanelController extends ActionController $this->view->assignMultiple([ 'filterSettings' => $filterSettings, 'filterOptions' => $filterOptions, + 'stimulusController' => 'mobile-search', ]); } diff --git a/public/typo3conf/ext/ep_products/Classes/Controller/AjaxSearchController.php b/public/typo3conf/ext/ep_products/Classes/Controller/AjaxSearchController.php index 6db3b79c..5448c451 100644 --- a/public/typo3conf/ext/ep_products/Classes/Controller/AjaxSearchController.php +++ b/public/typo3conf/ext/ep_products/Classes/Controller/AjaxSearchController.php @@ -99,12 +99,7 @@ class AjaxSearchController extends ActionController $organic = false; } - $filterOptions = $this->filterService->getFilterOptions( - $filterSettings, - $excludedConceptUids, - $excludedRegionUids, - $forcedConceptUids - ); + $filterOptions = $this->filterService->collectFilterOptionsFromResult($searchResult); $referringPageUri = $this ->uriBuilder @@ -124,6 +119,7 @@ class AjaxSearchController extends ActionController 'filterOptions' => $filterOptions, 'searchResult' => $searchResult->getData(), 'referringPageUri' => $referringPageUri, + 'stimulusController' => 'search', 'meta' => [ 'organic' => $organic, 'total' => $searchResult->getTotal(), diff --git a/public/typo3conf/ext/ep_products/Classes/Controller/SearchController.php b/public/typo3conf/ext/ep_products/Classes/Controller/SearchController.php index bb21e4cc..462dec39 100644 --- a/public/typo3conf/ext/ep_products/Classes/Controller/SearchController.php +++ b/public/typo3conf/ext/ep_products/Classes/Controller/SearchController.php @@ -140,12 +140,7 @@ class SearchController extends ActionController $organic = false; } - $filterOptions = $this->filterService->getFilterOptions( - $filterSettings, - $excludedConceptUids, - $excludedRegionUids, - $forcedConceptUids - ); + $filterOptions = $this->filterService->collectFilterOptionsFromResult($searchResult); $referringPageUri = $this ->uriBuilder @@ -165,6 +160,7 @@ class SearchController extends ActionController 'filterOptions' => $filterOptions, 'searchResult' => $searchResult->getData(), 'referringPageUri' => $referringPageUri, + 'stimulusController' => 'search', 'meta' => [ 'organic' => $organic, 'total' => $searchResult->getTotal(), diff --git a/public/typo3conf/ext/ep_products/Classes/Domain/Model/FilterOptions.php b/public/typo3conf/ext/ep_products/Classes/Domain/Model/FilterOptions.php index 92b9b190..592dae09 100644 --- a/public/typo3conf/ext/ep_products/Classes/Domain/Model/FilterOptions.php +++ b/public/typo3conf/ext/ep_products/Classes/Domain/Model/FilterOptions.php @@ -96,9 +96,17 @@ class FilterOptions implements \JsonSerializable */ protected $busAvailable; + /** + * @var int + */ + protected $resultCount; + public function __construct() { - $this->pax = range(1, 20); + $this->pax = [ + 0 => 'beliebig', + ...array_combine(range(1, 20), range(1, 20)) + ]; } /** @@ -277,6 +285,22 @@ class FilterOptions implements \JsonSerializable $this->busAvailable = $busAvailable; } + /** + * @return int + */ + public function getResultCount(): ?int + { + return $this->resultCount; + } + + /** + * @param int $resultCount + */ + public function setResultCount(int $resultCount) + { + $this->resultCount = $resultCount; + } + /** * @return array */ @@ -294,6 +318,7 @@ class FilterOptions implements \JsonSerializable 'dateFrom' => ($this->getDateFrom() !== null) ? $this->getDateFrom()->format('Y-m-d') : null, 'dateTo' => ($this->getDateTo() !== null) ? $this->getDateTo()->format('Y-m-d') : null, 'busAvailable' => $this->getBusAvailable(), + 'resultCount' => $this->getResultCount(), ]; } diff --git a/public/typo3conf/ext/ep_products/Classes/Domain/Repository/ProductRepository.php b/public/typo3conf/ext/ep_products/Classes/Domain/Repository/ProductRepository.php index 6f3759d8..537fd9a5 100644 --- a/public/typo3conf/ext/ep_products/Classes/Domain/Repository/ProductRepository.php +++ b/public/typo3conf/ext/ep_products/Classes/Domain/Repository/ProductRepository.php @@ -89,17 +89,22 @@ class ProductRepository extends AbstractRepository $query = $qb ->select( 'date.concept as conceptUid', 'date.concept_name as conceptName', 'date.concept_code as conceptCode', + 'date.concept_sorting as conceptSorting', 'date.product as productUid', 'date.product_name as productName', 'date.product_detail_page as productDetailPage', 'date.product_feature as productFeature', 'date.daytrip as daytrip', 'date.country as countryUid','date.country_name as countryName', 'date.country_code as countryCode', 'date.region as regionUid', 'date.region_name as regionName', 'date.region_feature as regionFeature', 'date.date_start as dateStart', 'date.date_end as dateEnd', 'date.hotel as hotelUid', 'date.hotel_name as hotelName', 'date.hotel_category as hotelCategory', + 'date.hotel_type as hotelType', 'date.earlybird as earlybird', 'date.new as new', 'date.low_contingent as lowContingent', - 'date.board as board', 'date.product_fact as productFact', 'date.hotel_fact as hotelFact', - 'date.region_fact as regionFact', 'date.services_included as servicesIncluded', + 'date.board as board', 'date.board_bus_pro_id as boardUid', + 'date.product_fact as productFact', 'date.hotel_fact as hotelFact', 'date.region_fact as regionFact', + 'date.services_included as servicesIncluded', + 'date.bus_available as busAvailable', 'date.min_price as minPrice', 'date.nights as nights', 'date.available as available', - 'date.pseudo_price as pseudoPrice', 'date.hide_booking_button as hideBookingButton' + 'date.pseudo_price as pseudoPrice', 'date.hide_booking_button as hideBookingButton', + 'room.type as roomType' ) ->from('tx_epproducts_domain_model_date', 'date') ->leftJoin('date', 'tx_epproducts_domain_model_room', 'room', 'room.date = date.uid') diff --git a/public/typo3conf/ext/ep_products/Classes/Service/FilterService.php b/public/typo3conf/ext/ep_products/Classes/Service/FilterService.php index c5e9f84f..0c40a163 100644 --- a/public/typo3conf/ext/ep_products/Classes/Service/FilterService.php +++ b/public/typo3conf/ext/ep_products/Classes/Service/FilterService.php @@ -28,6 +28,7 @@ namespace EP\EpProducts\Service; ***************************************************************/ use EP\EpProducts\Domain\Model\FilterOptions; +use EP\EpProducts\Domain\Model\SearchResult; use EP\EpProducts\Domain\Repository\DateRepository; use Symfony\Component\OptionsResolver\OptionsResolver; use TYPO3\CMS\Core\SingletonInterface; @@ -116,10 +117,10 @@ class FilterService implements SingletonInterface $filterSettings['priceRanges'] = [ $searchParams['priceRange'] ]; } if ($searchParams['pax']) { - $filterSettings['pax'] = $searchParams['pax']; + $filterSettings['pax'] = (int)$searchParams['pax']; } if ($searchParams['nights']) { - $filterSettings['nights'] = $searchParams['nights']; + $filterSettings['nights'] = (int)$searchParams['nights']; } return $filterSettings; @@ -157,7 +158,7 @@ class FilterService implements SingletonInterface $filterSettings['dateTo'] = $staticSearchParams['date_to']; } if (!empty($staticSearchParams['pax']) && (int)$staticSearchParams['pax'] > 0) { - $filterSettings['pax'] = $staticSearchParams['pax']; + $filterSettings['pax'] = (int)$staticSearchParams['pax']; } return $filterSettings; @@ -183,6 +184,124 @@ class FilterService implements SingletonInterface return $this->preprocessFilterOptions($filterOptionsData); } + public function collectFilterOptionsFromResult(SearchResult $searchResult): FilterOptions + { + $priceRanges = []; + $busAvailable = false; + $nights = []; + $concepts = []; + $destinations = []; + $hotelTypes = []; + $roomTypes = []; + $boardTypes = []; + $dateMin = $dateMax = null; + + foreach ($searchResult->getData() as $row) { + $concept = $row['concept']; + if (!array_key_exists($concept['uid'], $concepts)) { + $concepts[$concept['sorting']] = [ + 'uid' => $concept['uid'], + 'name' => $concept['name'], + 'code' => $concept['code'], + ]; + } + foreach ($row['items'] as $item) { + if (null === $dateMin || $item['minDateStart'] < $dateMin) { + $dateMin = $item['minDateStart']; + } + if (null === $dateMax || $item['maxDateEnd'] > $dateMax) { + $dateMax = $row['itemMaxDateEnd']; + } + if ((bool) $row['busAvailable'] === true) { + $busAvailable = true; + } + foreach ($item['nights'] as $nightsOption) { + $duration = (int) $nightsOption <= 4 ? self::DURATION_SHORT : self::DURATION_LONG; + if (!\in_array($duration, $nights, false)) { + $nights[] = $duration; + } + } + if (!array_key_exists($item['country']['uid'], $destinations)) { + $destinations[$item['country']['uid']] = [ + 'country' => [ + 'uid' => $item['country']['uid'], + 'label' => $item['country']['name'], + 'code' => $item['country']['code'], + ], + 'regions' => [], + ]; + } + if (!array_key_exists($item['region']['uid'], $destinations[$item['country']['uid']]['regions'])) { + $destinations[$item['country']['uid']]['regions'][$item['region']['uid']] = [ + 'uid' => $item['region']['uid'], + 'label' => $item['region']['name'], + ]; + } + if (!\in_array($item['hotel']['type'], $hotelTypes, false)) { + $hotelTypes[] = $item['hotel']['type']; + } + foreach ($item['rooms'] as $roomType) { + if (!\in_array($roomType, $roomTypes, false)) { + $roomTypes[] = $roomType; + } + } + $boardType = [ + 'uid' => $item['boardUid'], + 'label' => $item['board'], + ]; + if (!\in_array($boardType, $boardTypes, false)) { + $boardTypes[] = $boardType; + } + foreach (FilterOptions::$priceRangeOptions as $index => $priceRange) { + if (array_key_exists($index, $priceRanges)) { + continue; + } + [$min, $max] = $priceRange; + if ($item['minPrice'] >= $min && $item['minPrice'] < $max) { + $range = FilterOptions::$priceRangeOptions[$index]; + $priceRanges[$index] = [ + 'uid' => $index, + 'min' => $range[0], + 'max' => $range[1], + 'formatted' => $this->formatPriceRange($range[0], $range[1]), + ]; + } + } + } + } + + foreach ($destinations as $key => $value) { + usort($value['regions'], function ($a, $b) { + return strcmp($a['label'], $b['label']); + }); + $destinations[$key] = $value; + } + sort($destinations); + sort($nights); + sort($roomTypes); + ksort($priceRanges); + + usort($boardTypes, function ($a, $b) { + return strcmp($a['label'], $b['label']); + }); + + $filterOptions = new FilterOptions(); + $filterOptions->setPriceRanges($priceRanges); + $filterOptions->setDateFrom($dateMin); + $filterOptions->setDateTo($dateMax); + $filterOptions->setConcepts($concepts); + $filterOptions->setDestinations($destinations); + $filterOptions->setHotelTypes($hotelTypes); + $filterOptions->setRoomTypes($roomTypes); + $filterOptions->setBoardTypes($boardTypes); + $filterOptions->setBusAvailable($busAvailable); + $filterOptions->setNights($nights); + + $filterOptions->setResultCount($searchResult->getTotal()); + + return $filterOptions; + } + public function preprocessFilterOptions(array $filterOptionsData): FilterOptions { $priceRanges = []; @@ -256,15 +375,14 @@ class FilterService implements SingletonInterface // Board types $boardType = [ 'uid' => $row['boardUid'], - 'label' => $row['boardType'], + 'label' => $row['board'], ]; if (!\in_array($boardType, $boardTypes, false)) { $boardTypes[] = $boardType; } // Price range - foreach (FilterOptions::$priceRangeOptions as $index => $priceRange) - { + foreach (FilterOptions::$priceRangeOptions as $index => $priceRange) { if (array_key_exists($index, $priceRanges)) { continue; } diff --git a/public/typo3conf/ext/ep_products/Classes/Service/SearchResultService.php b/public/typo3conf/ext/ep_products/Classes/Service/SearchResultService.php index 7f6982e1..031ea0b0 100644 --- a/public/typo3conf/ext/ep_products/Classes/Service/SearchResultService.php +++ b/public/typo3conf/ext/ep_products/Classes/Service/SearchResultService.php @@ -108,6 +108,7 @@ class SearchResultService implements SingletonInterface 'uid' => $row['conceptUid'], 'name' => $row['conceptName'], 'code' => $row['conceptCode'], + 'sorting' => $row['conceptSorting'], ], 'items' => [], ]; @@ -130,6 +131,7 @@ class SearchResultService implements SingletonInterface 'earlybird' => $row['earlybird'], 'daytrip' => $row['daytrip'], 'hideBookingButton' => $row['hideBookingButton'], + 'busAvailable' => $row['busAvailable'], 'lowContingent' => $row['lowContingent'], 'servicesIncluded' => unserialize($row['servicesIncluded'], ['allowed_classes' => false]), 'hotel' => [ @@ -139,13 +141,16 @@ class SearchResultService implements SingletonInterface 'images' => $this->hotelImageService->getImages($row['hotelUid']), 'fact' => $row['hotelFact'], 'category' => $hotelCategory, + 'type' => $row['hotelType'], ], 'country' => [ + 'uid' => $row['countryUid'], 'code' => $row['countryCode'], 'name' => $row['countryName'], 'flag' => $this->flagIconService->getImage($row['countryCode']), ], 'region' => [ + 'uid' => $row['regionUid'], 'name' => $row['regionName'], 'fact' => $row['regionFact'], 'feature' => $row['regionFeature'], @@ -159,14 +164,20 @@ class SearchResultService implements SingletonInterface 'fact' => $row['productFact'], ], 'board' => $row['board'], + 'boardUid' => $row['boardUid'], 'dates' => [], 'nights' => [], + 'rooms' => [], ]; $total++; } $item =& $items[$key]; + if (!\in_array($row['roomType'], $item['rooms'], false)) { + $item['rooms'][] = $row['roomType']; + } + if (!\in_array($row['nights'], $item['nights'], false)) { $item['nights'][] = $row['nights']; } diff --git a/public/typo3conf/ext/ep_theme/Resources/Private/Assets/js/controllers/mobile_search_controller.js b/public/typo3conf/ext/ep_theme/Resources/Private/Assets/js/controllers/mobile_search_controller.js index ceab96be..d9181f21 100644 --- a/public/typo3conf/ext/ep_theme/Resources/Private/Assets/js/controllers/mobile_search_controller.js +++ b/public/typo3conf/ext/ep_theme/Resources/Private/Assets/js/controllers/mobile_search_controller.js @@ -13,8 +13,10 @@ export default class extends Controller { static values = { optionsUri: String, + initialOptionsUri: String, searchUri: String, loading: Boolean, + loaded: Boolean, show: Boolean, } @@ -25,13 +27,14 @@ export default class extends Controller { open(e) { if (e.detail === 'mobile-search') { this.showValue = true - this.load() + if (false === this.loadedValue) { + this.load() + } } } close() { this.showValue = false - this.containerTarget.innerHTML = '' } filter() { @@ -46,12 +49,14 @@ export default class extends Controller { } load(options) { + let uri = this.loadedValue ? this.optionsUriValue : this.initialOptionsUriValue this.loadingValue = true - fetch(this.optionsUriValue, options) + fetch(uri, options) .then(this.checkStatus) .then(this.parseHTML) .then(html => { this.containerTarget.innerHTML = html + this.loadedValue = true }) .finally(() => { this.loadingValue = false @@ -73,6 +78,7 @@ export default class extends Controller { } selectDateRange(e) { + e.stopPropagation() this.dateFromTarget.value = e.detail.from this.dateToTarget.value = e.detail.to this.filter() diff --git a/public/typo3conf/ext/ep_theme/Resources/Private/Partials/Search/FilterPanel.html b/public/typo3conf/ext/ep_theme/Resources/Private/Partials/Search/FilterPanel.html index a6981dbc..10001208 100644 --- a/public/typo3conf/ext/ep_theme/Resources/Private/Partials/Search/FilterPanel.html +++ b/public/typo3conf/ext/ep_theme/Resources/Private/Partials/Search/FilterPanel.html @@ -34,8 +34,7 @@ data-action="click->collapse#toggle" class="w-full px-0 bg-transparent border-0 ring-0 focus:ring-0 text-white uppercase cursor-pointer placeholder-white" placeholder="Zeitraum" - value="{ep:dateRange(dateFrom: filterSettings.dateFrom, dateTo: filterSettings.dateTo, includeLabel: 0)}" - data-searchbar-target="dateRangeLabel"/> + value="{ep:dateRange(dateFrom: filterSettings.dateFrom, dateTo: filterSettings.dateTo, includeLabel: 0)}"/> @@ -106,7 +105,7 @@ {concept.name} @@ -141,7 +140,7 @@