WIP Migrate filter panel

This commit is contained in:
Björn Fromme
2021-07-27 21:08:04 +02:00
parent 64b9f97fd2
commit 5f63d088de
29 changed files with 598 additions and 951 deletions
@@ -1,108 +0,0 @@
<?php
namespace EP\EpProducts\Controller;
/***************************************************************
*
* Copyright notice
*
* (c) 2016 Björn Fromme <[email protected]>, dreipunktnull
*
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/
use EP\EpProducts\Domain\Model\FilterSettings;
use EP\EpProducts\Service\DateService;
use EP\EpProducts\Service\FilterService;
use EP\EpProducts\Traits\RequestArgumentTypeConversionTrait;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
class AjaxFilterpanelController extends ActionController
{
use RequestArgumentTypeConversionTrait;
/**
* @var \EP\EpProducts\Service\FilterService
*/
protected $filterService;
/**
* @var DateService
*/
protected $dateService;
/**
* @param FilterService $filterService
*/
public function __construct(FilterService $filterService, DateService $dateService)
{
parent::__construct();
$this->filterService = $filterService;
$this->dateService = $dateService;
}
public function initializeAction()
{
$forcedConceptUids = GeneralUtility::trimExplode(',', $this->settings['forcedConceptUids'], true);
$this->processFilterSettingsArgument($forcedConceptUids);
}
/**
* @param FilterSettings $filterSettings
*/
public function panelAction(FilterSettings $filterSettings)
{
$excludedConceptUids = GeneralUtility::trimExplode(',', $this->settings['excludedConceptUids']);
$excludedRegionUids = GeneralUtility::trimExplode(',', $this->settings['excludedRegionUids']);
$filterOptions = $this->filterService->getFilterOptions(
$filterSettings,
$excludedConceptUids,
$excludedRegionUids
);
$this->view->assignMultiple([
'filterOptions' => $filterOptions,
'filterSettings' => $filterSettings,
]);
}
/**
* @param FilterSettings $filterSettings
* @param string $search
*
* @return string
*/
public function autocompleteAction(FilterSettings $filterSettings, $search)
{
$excludedConceptUids = GeneralUtility::trimExplode(',', $this->settings['excludedConceptUids']);
$excludedRegionUids = GeneralUtility::trimExplode(',', $this->settings['excludedRegionUids']);
$destinations = $this->dateService->getAutocompleteOptions(
$filterSettings,
$search,
$excludedConceptUids,
$excludedRegionUids
);
return json_encode($destinations, JSON_THROW_ON_ERROR);
}
}
@@ -92,6 +92,7 @@ class AjaxSearchController extends ActionController
$organic = true; $organic = true;
$forcedConceptUids = GeneralUtility::trimExplode(',', $this->settings['forcedConceptUids'], true); $forcedConceptUids = GeneralUtility::trimExplode(',', $this->settings['forcedConceptUids'], true);
$excludedConceptUids = GeneralUtility::trimExplode(',', $this->settings['excludedConceptUids']); $excludedConceptUids = GeneralUtility::trimExplode(',', $this->settings['excludedConceptUids']);
$excludedRegionUids = GeneralUtility::trimExplode(',', $this->settings['excludedRegionUids']);
if ($filterSettings === null) { if ($filterSettings === null) {
$filterSettings = $this->filterService->getDefaultFilterSettings($forcedConceptUids); $filterSettings = $this->filterService->getDefaultFilterSettings($forcedConceptUids);
@@ -115,6 +116,13 @@ class AjaxSearchController extends ActionController
$this->searchResultUrlService->process($searchResult, $options); $this->searchResultUrlService->process($searchResult, $options);
$filterOptions = $this->filterService->getFilterOptions(
$filterSettings,
$excludedConceptUids,
$excludedRegionUids,
$forcedConceptUids
);
$referringPageUri = $this $referringPageUri = $this
->uriBuilder ->uriBuilder
->reset() ->reset()
@@ -129,12 +137,13 @@ class AjaxSearchController extends ActionController
; ;
$this->view->assignMultiple([ $this->view->assignMultiple([
'filterSettings' => $filterSettings,
'filterOptions' => $filterOptions,
'searchResult' => $searchResult->getData(), 'searchResult' => $searchResult->getData(),
'referringPageUri' => $referringPageUri, 'referringPageUri' => $referringPageUri,
'meta' => [ 'meta' => [
'organic' => $organic, 'organic' => $organic,
'total' => $searchResult->getTotal(), 'total' => $searchResult->getTotal(),
'destinationName' => $filterSettings->getDestinationName(),
'dateRange' => DateUtility::formatDateRange($filterSettings->getDateFrom(), $filterSettings->getDateTo()), 'dateRange' => DateUtility::formatDateRange($filterSettings->getDateFrom(), $filterSettings->getDateTo()),
'dateSelected' => $dateSelected, 'dateSelected' => $dateSelected,
], ],
@@ -55,8 +55,7 @@ class AjaxSearchbarController extends ActionController
public function initializeAction() public function initializeAction()
{ {
$forcedConceptUids = GeneralUtility::trimExplode(',', $this->settings['forcedConceptUids'], true); $this->processSearchParamsArgument();
$this->processSearchParamsArgument($forcedConceptUids);
} }
/** /**
@@ -65,16 +64,23 @@ class AjaxSearchbarController extends ActionController
*/ */
public function updateAction(SearchParams $searchParams = null) public function updateAction(SearchParams $searchParams = null)
{ {
$forcedConceptUids = GeneralUtility::trimExplode(',', $this->settings['forcedConceptUids'], true);
if ($searchParams === null) { if ($searchParams === null) {
$searchParams = new SearchParams(); $searchParams = new SearchParams();
$forcedConceptUids = GeneralUtility::trimExplode(',', $this->settings['forcedConceptUids'], true);
$filterSettings = $this->filterService->getDefaultFilterSettings($forcedConceptUids); $filterSettings = $this->filterService->getDefaultFilterSettings($forcedConceptUids);
} else { } else {
$filterSettings = FilterSettings::fromSearchParams($searchParams); $filterSettings = FilterSettings::fromSearchParams($searchParams);
} }
$excludedConceptUids = GeneralUtility::trimExplode(',', $this->settings['excludedConceptUids']); $excludedConceptUids = GeneralUtility::trimExplode(',', $this->settings['excludedConceptUids']);
$excludedRegionUids = GeneralUtility::trimExplode(',', $this->settings['excludedRegionUids']); $excludedRegionUids = GeneralUtility::trimExplode(',', $this->settings['excludedRegionUids']);
$filterOptions = $this->filterService->getFilterOptions($filterSettings, $excludedConceptUids, $excludedRegionUids); $filterOptions = $this->filterService->getFilterOptions(
$filterSettings,
$excludedConceptUids,
$excludedRegionUids,
$forcedConceptUids
);
return json_encode([ return json_encode([
'filterOptions' => $filterOptions, 'filterOptions' => $filterOptions,
'searchParams' => $searchParams, 'searchParams' => $searchParams,
@@ -33,7 +33,6 @@ use EP\EpProducts\Domain\Repository\HotelRepository;
use EP\EpProducts\Domain\Repository\ProductRepository; use EP\EpProducts\Domain\Repository\ProductRepository;
use EP\EpProducts\Service\DateService; use EP\EpProducts\Service\DateService;
use EP\EpProducts\Service\FilterService; use EP\EpProducts\Service\FilterService;
use EP\EpProducts\Service\FilterSettingsEncoder;
use EP\EpProducts\Service\HotelDataService; use EP\EpProducts\Service\HotelDataService;
use EP\EpProducts\Service\ProductDataService; use EP\EpProducts\Service\ProductDataService;
use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\GeneralUtility;
@@ -119,12 +118,11 @@ class ProductController extends ActionController
// Redirect to search page in case no product can be determined // Redirect to search page in case no product can be determined
if ($product === null) { if ($product === null) {
$filterSettings = $this->filterService->getDefaultFilterSettings(); $filterSettings = $this->filterService->getDefaultFilterSettings();
$filterSettingsEncoded = FilterSettingsEncoder::encode($filterSettings);
$this->redirect( $this->redirect(
'searchresult', 'searchresult',
'Search', 'Search',
null, null,
['filterSettings' => $filterSettingsEncoded], ['filterSettings' => $filterSettings->toArray()],
$this->settings['defaultSearchPageUid'] $this->settings['defaultSearchPageUid']
); );
} }
@@ -32,7 +32,6 @@ use EP\EpProducts\Domain\Model\SearchParams;
use EP\EpProducts\Service\FilterService; use EP\EpProducts\Service\FilterService;
use EP\EpProducts\Service\FilterSettingsEncoder; use EP\EpProducts\Service\FilterSettingsEncoder;
use EP\EpProducts\Traits\RequestArgumentTypeConversionTrait; use EP\EpProducts\Traits\RequestArgumentTypeConversionTrait;
use TYPO3\CMS\Core\Log\LogManager;
use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
use TYPO3\CMS\Extbase\Mvc\Exception\StopActionException; use TYPO3\CMS\Extbase\Mvc\Exception\StopActionException;
@@ -64,18 +63,15 @@ class SearchController extends ActionController
{ {
parent::__construct(); parent::__construct();
$this->filterService = $filterService; $this->filterService = $filterService;
}
public function initializeAction()
{
$forcedConceptUids = GeneralUtility::trimExplode(',', $this->settings['forcedConceptUids'], true); $forcedConceptUids = GeneralUtility::trimExplode(',', $this->settings['forcedConceptUids'], true);
$excludedConceptUids = GeneralUtility::trimExplode(',', $this->settings['excludedConceptUids'], true);
$excludedRegionUids = GeneralUtility::trimExplode(',', $this->settings['excludedRegionUids'], true);
$this->defaultFilterSettings = $this->filterService->getDefaultFilterSettings($forcedConceptUids); $this->defaultFilterSettings = $this->filterService->getDefaultFilterSettings($forcedConceptUids);
$excludedConceptUids = GeneralUtility::trimExplode(',', $this->settings['excludedConceptUids']);
$excludedRegionUids = GeneralUtility::trimExplode(',', $this->settings['excludedRegionUids']);
$this->defaultFilterOptions = $this->filterService->getFilterOptions( $this->defaultFilterOptions = $this->filterService->getFilterOptions(
$this->defaultFilterSettings, $this->defaultFilterSettings,
$excludedConceptUids, $excludedConceptUids,
$excludedRegionUids $excludedRegionUids,
$forcedConceptUids
); );
} }
@@ -103,8 +99,7 @@ class SearchController extends ActionController
public function initializeProcessSearchAction() public function initializeProcessSearchAction()
{ {
$forcedConceptUids = GeneralUtility::trimExplode(',', $this->settings['forcedConceptUids'], true); $this->processSearchParamsArgument();
$this->processSearchParamsArgument($forcedConceptUids);
} }
/** /**
@@ -143,11 +138,13 @@ class SearchController extends ActionController
{ {
$excludedConceptUids = GeneralUtility::trimExplode(',', $this->settings['excludedConceptUids']); $excludedConceptUids = GeneralUtility::trimExplode(',', $this->settings['excludedConceptUids']);
$excludedRegionUids = GeneralUtility::trimExplode(',', $this->settings['excludedRegionUids']); $excludedRegionUids = GeneralUtility::trimExplode(',', $this->settings['excludedRegionUids']);
$forcedConceptUids = GeneralUtility::trimExplode(',', $this->settings['forcedConceptUids'], true);
$filterOptions = $this->filterService->getFilterOptions( $filterOptions = $this->filterService->getFilterOptions(
$filterSettings, $filterSettings,
$excludedConceptUids, $excludedConceptUids,
$excludedRegionUids $excludedRegionUids,
$forcedConceptUids
); );
$this->view->assignMultiple([ $this->view->assignMultiple([
@@ -172,21 +169,4 @@ class SearchController extends ActionController
['filterSettings' => $filterSettings] ['filterSettings' => $filterSettings]
); );
} }
/**
* @param SearchParams $searchParams
*/
private function logSearch(SearchParams $searchParams)
{
$ignoreIps = GeneralUtility::trimExplode(',', $this->settings['searchLogIgnoreIps'], true);
$remoteIp = GeneralUtility::getIndpEnv('REMOTE_ADDR');
if (\in_array($remoteIp, $ignoreIps, false)) {
return;
}
/** @var $logger \TYPO3\CMS\Core\Log\Logger */
$logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__);
$logger->info('Searchresult processed', [
'searchParams' => $searchParams,
]);
}
} }
@@ -29,6 +29,18 @@ namespace EP\EpProducts\Domain\Model;
class FilterOptions implements \JsonSerializable class FilterOptions implements \JsonSerializable
{ {
/**
* @var array
*/
public static $priceRangeOptions = [
1 => [ 1, 200 ],
2 => [ 200, 300 ],
3 => [ 300, 400 ],
4 => [ 400, 500 ],
5 => [ 500, 600 ],
6 => [ 600, 999 ],
];
/** /**
* @var array * @var array
*/ */
@@ -34,46 +34,13 @@ use TYPO3\CMS\Extbase\Mvc\Request;
class FilterSettings implements \JsonSerializable class FilterSettings implements \JsonSerializable
{ {
const DESTINATION_TYPE_COUNTRY = 'country'; public const DURATION_SHORT = 4;
const DESTINATION_TYPE_REGION = 'region'; public const DURATION_LONG = 5;
const DESTINATION_TYPE_CITY = 'city';
const DESTINATION_TYPE_HOTEL = 'hotel';
const DESTINATION_TYPE_PRODUCT = 'product';
const DURATION_SHORT = 4;
const DURATION_LONG = 5;
/** /**
* @var array * @var array
*/ */
public static $priceRanges = [ protected $priceRanges = [];
1 => [ 1, 200 ],
2 => [ 200, 300 ],
3 => [ 300, 400 ],
4 => [ 400, 500 ],
5 => [ 500, 600 ],
6 => [ 600, 999 ],
];
/**
* @var int
*/
protected $priceRange = 0;
/**
* @var string
*/
protected $destinationName = '';
/**
* @var int
*/
protected $destinationUid = 0;
/**
* @var string
*/
protected $destinationType = '';
/** /**
* @var array * @var array
@@ -159,26 +126,39 @@ class FilterSettings implements \JsonSerializable
public static function fromSearchParams(SearchParams $searchParams): self public static function fromSearchParams(SearchParams $searchParams): self
{ {
$filterSettings = new self; $filterSettings = new self;
$filterSettings->setDateFrom($searchParams->getDateFrom());
$filterSettings->setDateTo($searchParams->getDateTo());
$filterSettings->setDestinationUid($searchParams->getDestinationUid());
$filterSettings->setDestinationType($searchParams->getDestinationType());
if ($searchParams->getDestinationType() && $searchParams->getDestinationUid()) { if ($searchParams->getDestinationType() && $searchParams->getDestinationUid()) {
$destinationUid = $searchParams->getDestinationUid();
switch ($searchParams->getDestinationType()) { switch ($searchParams->getDestinationType()) {
case self::DESTINATION_TYPE_COUNTRY: case SearchParams::DESTINATION_TYPE_COUNTRY:
$filterSettings->setCountryUids([$searchParams->getDestinationUid()]); $filterSettings->setCountryUids([$destinationUid]);
break; break;
case self::DESTINATION_TYPE_REGION: case SearchParams::DESTINATION_TYPE_REGION:
$filterSettings->setRegionUids([$searchParams->getDestinationUid()]); $filterSettings->setRegionUids([$destinationUid]);
break;
case SearchParams::DESTINATION_TYPE_CITY:
$filterSettings->setCityUids([$destinationUid]);
break;
case SearchParams::DESTINATION_TYPE_CONCEPT:
$filterSettings->setConceptUids([$destinationUid]);
break; break;
} }
} }
if ($searchParams->getConceptUids()) { if (null !== $searchParams->getDateFrom()) {
$filterSettings->setConceptUids($searchParams->getConceptUids()); $filterSettings->setDateFrom($searchParams->getDateFrom());
} }
$filterSettings->setPriceRange($searchParams->getPriceRange()); if (null !== $searchParams->getDateTo()) {
$filterSettings->setPax($searchParams->getPax()); $filterSettings->setDateTo($searchParams->getDateTo());
$filterSettings->setNights($searchParams->getNights()); }
if (null !== $searchParams->getPriceRange()) {
$filterSettings->setPriceRanges([$searchParams->getPriceRange()]);
}
if (null !== $searchParams->getPax()) {
$filterSettings->setPax($searchParams->getPax());
}
if (null !== $searchParams->getNights()) {
$filterSettings->setNights($searchParams->getNights());
}
return $filterSettings; return $filterSettings;
} }
@@ -256,67 +236,19 @@ class FilterSettings implements \JsonSerializable
} }
/** /**
* @return int * @return array
*/ */
public function getPriceRange() public function getPriceRanges()
{ {
return $this->priceRange; return $this->priceRanges;
} }
/** /**
* @param int $priceRange * @param array $priceRanges
*/ */
public function setPriceRange($priceRange) public function setPriceRanges($priceRanges)
{ {
$this->priceRange = $priceRange; $this->priceRanges = $priceRanges;
}
/**
* @return string
*/
public function getDestinationName()
{
return $this->destinationName;
}
/**
* @param string $destinationName
*/
public function setDestinationName($destinationName)
{
$this->destinationName = $destinationName;
}
/**
* @return int
*/
public function getDestinationUid()
{
return $this->destinationUid;
}
/**
* @param int $destinationUid
*/
public function setDestinationUid($destinationUid)
{
$this->destinationUid = (int) $destinationUid;
}
/**
* @return string
*/
public function getDestinationType()
{
return $this->destinationType;
}
/**
* @param string $destinationType
*/
public function setDestinationType($destinationType)
{
$this->destinationType = $destinationType;
} }
/** /**
@@ -542,22 +474,10 @@ class FilterSettings implements \JsonSerializable
$this->bus = $bus; $this->bus = $bus;
} }
/**
* @return bool
*/
public function getDestinationSelected()
{
return $this->destinationType !== '';
}
public function toArray(): array public function toArray(): array
{ {
return [ return [
'priceRange' => $this->getPriceRange(), 'priceRanges' => $this->getPriceRanges(),
'destinationName' => $this->getDestinationName(),
'destinationUid' => $this->getDestinationUid(),
'destinationType' => $this->getDestinationType(),
'countryUids' => $this->getCountryUids(), 'countryUids' => $this->getCountryUids(),
'regionUids' => $this->getRegionUids(), 'regionUids' => $this->getRegionUids(),
'cityUids' => $this->getCityUids(), 'cityUids' => $this->getCityUids(),
@@ -32,10 +32,15 @@ use TYPO3\CMS\Extbase\Mvc\Request;
class SearchParams implements \JsonSerializable class SearchParams implements \JsonSerializable
{ {
public const DESTINATION_TYPE_COUNTRY = 'country';
public const DESTINATION_TYPE_REGION = 'region';
public const DESTINATION_TYPE_CITY = 'city';
public const DESTINATION_TYPE_CONCEPT = 'concept';
/** /**
* @var int * @var int
*/ */
protected $priceRange = 0; protected $priceRange;
/** /**
* @var \DateTime * @var \DateTime
@@ -50,22 +55,17 @@ class SearchParams implements \JsonSerializable
/** /**
* @var int * @var int
*/ */
protected $destinationUid = 0; protected $destinationUid;
/** /**
* @var string * @var string
*/ */
protected $destinationType; protected $destinationType;
/**
* @var array
*/
protected $conceptUids = [];
/** /**
* @var int * @var int
*/ */
protected $nights = 0; protected $nights;
/** /**
* @var int * @var int
@@ -75,7 +75,7 @@ class SearchParams implements \JsonSerializable
/** /**
* @var int * @var int
*/ */
protected $pageUid = 0; protected $pageUid;
/** /**
* @param int $pageUid * @param int $pageUid
@@ -187,22 +187,6 @@ class SearchParams implements \JsonSerializable
$this->destinationType = $destinationType; $this->destinationType = $destinationType;
} }
/**
* @return array
*/
public function getConceptUids()
{
return $this->conceptUids;
}
/**
* @param array $conceptUids
*/
public function setConceptUids(array $conceptUids)
{
$this->conceptUids = $conceptUids;
}
/** /**
* @return int * @return int
*/ */
@@ -257,7 +241,6 @@ class SearchParams implements \JsonSerializable
'priceRange' => $this->getPriceRange(), 'priceRange' => $this->getPriceRange(),
'destinationUid' => $this->getDestinationUid(), 'destinationUid' => $this->getDestinationUid(),
'destinationType' => $this->getDestinationType(), 'destinationType' => $this->getDestinationType(),
'conceptUids' => $this->getConceptUids(),
'nights' => $this->getNights(), 'nights' => $this->getNights(),
'pax' => $this->getPax(), 'pax' => $this->getPax(),
'pageUid' => $this->getPageUid(), 'pageUid' => $this->getPageUid(),
@@ -27,6 +27,7 @@ namespace EP\EpProducts\Domain\Repository;
* This copyright notice MUST APPEAR in all copies of the script! * This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/ ***************************************************************/
use EP\EpProducts\Domain\Model\FilterOptions;
use EP\EpProducts\Domain\Model\FilterSettings; use EP\EpProducts\Domain\Model\FilterSettings;
use EP\EpProducts\Traits\DbConnectionTrait; use EP\EpProducts\Traits\DbConnectionTrait;
use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\Connection;
@@ -163,16 +164,21 @@ abstract class AbstractRepository extends \TYPO3\CMS\Extbase\Persistence\Reposit
*/ */
protected function addPriceRangeFilterConditions(QueryBuilder $query, FilterSettings $filterSettings) protected function addPriceRangeFilterConditions(QueryBuilder $query, FilterSettings $filterSettings)
{ {
// Price range // Price ranges
$priceRange = (int) $filterSettings->getPriceRange(); if (count($filterSettings->getPriceRanges()) > 0) {
if ($priceRange > 0) { $orX = $query->expr()->orX();
[ $priceMin, $priceMax ] = FilterSettings::$priceRanges[$priceRange]; foreach ($filterSettings->getPriceRanges() as $priceRange) {
$query [ $priceMin, $priceMax ] = FilterOptions::$priceRangeOptions[$priceRange];
->andWhere('date.min_price >= :priceMin') $orX->add($query->expr()->andX(
->andWhere('date.min_price < :priceMax') $query->expr()->gte('date.min_price', ':priceMin'),
->setParameter('priceMin', $priceMin) $query->expr()->lt('date.min_price', ':priceMax')
->setParameter('priceMax', $priceMax) ));
; $query
->setParameter('priceMin', $priceMin)
->setParameter('priceMax', $priceMax)
;
}
$query->andWhere($orX);
} }
} }
@@ -72,21 +72,18 @@ class DateRepository extends AbstractRepository
return [$dateRangeFrom, $dateRangeTo]; return [$dateRangeFrom, $dateRangeTo];
} }
/** public function getFilterOptions(
* @param FilterSettings $filterSettings FilterSettings $filterSettings,
* @param array $excludedConceptUids array $excludedConceptUids = [],
* @param array $excludedRegionUids array $excludedRegionUids = [],
* @return array array $forcedConceptUids = []
* @throws \Doctrine\DBAL\DBALException ) {
*/
public function getFilterOptions(FilterSettings $filterSettings, $excludedConceptUids = [], $excludedRegionUids = [])
{
$qb = $this->getDbConnection()->createQueryBuilder(); $qb = $this->getDbConnection()->createQueryBuilder();
$query = $qb $query = $qb
->select( ->select(
'date.concept as conceptUid', 'date.concept_name as conceptName', 'date.concept_sorting as conceptSorting', 'date.concept as conceptUid', 'date.concept_name as conceptName', 'date.concept_sorting as conceptSorting',
'date.concept_code as conceptCode', 'date.min_price as minPrice', 'date.concept_code as conceptCode', 'date.min_price as minPrice', 'date.date_start as dateStart', 'date.date_end as dateEnd',
'date.country as countryUid','date.country_name as countryName', 'date.country_code as countryCode', '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 as regionUid', 'date.region_name as regionName',
'date.nights as nights', 'date.board as boardType', 'date.board_bus_pro_id as boardUid', 'date.nights as nights', 'date.board as boardType', 'date.board_bus_pro_id as boardUid',
@@ -120,6 +117,13 @@ class DateRepository extends AbstractRepository
; ;
} }
if (count($forcedConceptUids) > 0) {
$query
->andWhere('date.concept IN (:forcedConceptUids)')
->setParameter('forcedConceptUids', $forcedConceptUids, Connection::PARAM_INT_ARRAY)
;
}
$this->addFiltersettingsConditions($query, $filterSettings); $this->addFiltersettingsConditions($query, $filterSettings);
return $query->execute()->fetchAllAssociative(); return $query->execute()->fetchAllAssociative();
@@ -27,10 +27,8 @@ namespace EP\EpProducts\Service;
* This copyright notice MUST APPEAR in all copies of the script! * This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/ ***************************************************************/
use EP\EpProducts\Domain\Model\Date;
use EP\EpProducts\Domain\Model\FilterOptions; use EP\EpProducts\Domain\Model\FilterOptions;
use EP\EpProducts\Domain\Model\FilterSettings; use EP\EpProducts\Domain\Model\FilterSettings;
use EP\EpProducts\Domain\Model\Product;
use EP\EpProducts\Domain\Repository\DateRepository; use EP\EpProducts\Domain\Repository\DateRepository;
use TYPO3\CMS\Core\SingletonInterface; use TYPO3\CMS\Core\SingletonInterface;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility; use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
@@ -57,17 +55,14 @@ class FilterService implements SingletonInterface
$this->dateRepository = $dateRepository; $this->dateRepository = $dateRepository;
} }
/** public function getDefaultFilterSettings
* @param array $forcedConceptUids (
* @param bool $respectBookableDaterange array $forcedConceptUids = [],
* @param string $dateFromYmd bool $respectBookableDaterange = true,
* @param string $dateToYmd string $dateFromYmd = null,
* @return FilterSettings string $dateToYmd = null
* @throws \Exception ): FilterSettings {
*/ [ $dateFrom, $dateTo ] = $this->constrainDateRange($this->dateRepository->getDateRange());
public function getDefaultFilterSettings(array $forcedConceptUids = [], $respectBookableDaterange = true, $dateFromYmd = null, $dateToYmd = null)
{
[ $dateFrom, $dateTo ] = $this->constrainDateRange($this->getDateRange());
if (null !== $dateFromYmd) { if (null !== $dateFromYmd) {
$dateFrom = new \DateTime($dateFromYmd); $dateFrom = new \DateTime($dateFromYmd);
@@ -87,37 +82,27 @@ class FilterService implements SingletonInterface
return $filterSettings; return $filterSettings;
} }
/** public function getFilterOptions
* @param FilterSettings $filterSettings (
* @param array $excludedConceptUids FilterSettings $filterSettings,
* @param array $excludedRegionUids array $excludedConceptUids = [],
* @return FilterOptions array $excludedRegionUids = [],
* @throws \Doctrine\DBAL\DBALException array $forcedConceptUids = []
*/ ): FilterOptions {
public function getFilterOptions(FilterSettings $filterSettings, $excludedConceptUids = [], $excludedRegionUids = []) $filterOptionsData = $this->dateRepository->getFilterOptions(
{ $filterSettings,
$key = md5(serialize($filterSettings) . serialize($excludedConceptUids). serialize($excludedRegionUids)); $excludedConceptUids,
if (empty(static::$cache['filterOptions'][$key])) { $excludedRegionUids,
static::$cache['filterOptions'][$key] = $this->dateRepository->getFilterOptions( $forcedConceptUids
$filterSettings, );
$excludedConceptUids,
$excludedRegionUids
);
}
$filterOptionsData = static::$cache['filterOptions'][$key];
if (count($filterOptionsData) === 0) { if (count($filterOptionsData) === 0) {
return new FilterOptions(); return new FilterOptions();
} }
$dateRange = $this->constrainDateRange($this->getDateRange());
return $this->preprocessFilterOptions($filterOptionsData, $dateRange); return $this->preprocessFilterOptions($filterOptionsData);
} }
/** public function preprocessFilterOptions(array $filterOptionsData): FilterOptions
* @param array $filterOptionsData
* @param array $dateRange
* @return FilterOptions
*/
public function preprocessFilterOptions(array $filterOptionsData, array $dateRange)
{ {
$priceRanges = []; $priceRanges = [];
$busAvailable = false; $busAvailable = false;
@@ -127,12 +112,21 @@ class FilterService implements SingletonInterface
$hotelTypes = []; $hotelTypes = [];
$roomTypes = []; $roomTypes = [];
$boardTypes = []; $boardTypes = [];
$dateMin = $dateMax = null;
$absPath = ExtensionManagementUtility::extPath('ep_theme'); $absPath = ExtensionManagementUtility::extPath('ep_theme');
$baseUrl = '/' . PathUtility::stripPathSitePrefix($absPath); $baseUrl = '/' . PathUtility::stripPathSitePrefix($absPath);
foreach ($filterOptionsData as $row) foreach ($filterOptionsData as $row)
{ {
// Date
if (null === $dateMin || $row['dateStart'] < $dateMin) {
$dateMin = $row['dateStart'];
}
if (null === $dateMax || $row['dateEnd'] > $dateMax) {
$dateMax = $row['dateEnd'];
}
// Bus // Bus
if ((bool) $row['busAvailable'] === true) { if ((bool) $row['busAvailable'] === true) {
$busAvailable = true; $busAvailable = true;
@@ -193,14 +187,14 @@ class FilterService implements SingletonInterface
} }
// Price range // Price range
foreach (FilterSettings::$priceRanges as $index => $priceRange) foreach (FilterOptions::$priceRangeOptions as $index => $priceRange)
{ {
if (array_key_exists($index, $priceRanges)) { if (array_key_exists($index, $priceRanges)) {
continue; continue;
} }
[$min, $max] = $priceRange; [$min, $max] = $priceRange;
if ($row['minPrice'] >= $min && $row['minPrice'] < $max) { if ($row['minPrice'] >= $min && $row['minPrice'] < $max) {
$range = FilterSettings::$priceRanges[$index]; $range = FilterOptions::$priceRangeOptions[$index];
$priceRanges[$index] = [ $priceRanges[$index] = [
'uid' => $index, 'uid' => $index,
'min' => $range[0], 'min' => $range[0],
@@ -225,12 +219,10 @@ class FilterService implements SingletonInterface
return strcmp($a['label'], $b['label']); return strcmp($a['label'], $b['label']);
}); });
list ($dateMin, $dateMax) = $dateRange;
$filterOptions = new FilterOptions(); $filterOptions = new FilterOptions();
$filterOptions->setPriceRanges($priceRanges); $filterOptions->setPriceRanges($priceRanges);
$filterOptions->setDateFrom($dateMin); $filterOptions->setDateFrom(new \DateTime($dateMin));
$filterOptions->setDateTo($dateMax); $filterOptions->setDateTo(new \DateTime($dateMax));
$filterOptions->setConcepts($concepts); $filterOptions->setConcepts($concepts);
$filterOptions->setDestinations($destinations); $filterOptions->setDestinations($destinations);
$filterOptions->setHotelTypes($hotelTypes); $filterOptions->setHotelTypes($hotelTypes);
@@ -242,42 +234,7 @@ class FilterService implements SingletonInterface
return $filterOptions; return $filterOptions;
} }
/** public function constrainDateRange(array $dateRange, FilterSettings $filterSettings = null): array
* @param FilterSettings $filterSettings
*
* @return array
*/
public function getDateRange(FilterSettings $filterSettings = null)
{
$key = md5(serialize($filterSettings));
if (empty(static::$cache['dateRange'][$key])) {
static::$cache['dateRange'][$key] = $this->dateRepository->getDateRange();
}
return static::$cache['dateRange'][$key];
}
/**
* @param Product $product
* @param FilterSettings $filterSettings
*
* @return array
*/
public function getProductDateRange(Product $product, FilterSettings $filterSettings = null)
{
$key = md5($product->getUid() . serialize($filterSettings));
if (empty(static::$cache['productDateRange'][$key])) {
static::$cache['productDateRange'][$key] = $this->dateRepository->getDateRange($product);
}
return $this->constrainDateRange(static::$cache['productDateRange'][$key], $filterSettings);
}
/**
* @param array $dateRange
* @param FilterSettings $filterSettings
*
* @return array
*/
public function constrainDateRange(array $dateRange, FilterSettings $filterSettings = null)
{ {
[ $dateFrom, $dateTo ] = $dateRange; [ $dateFrom, $dateTo ] = $dateRange;
if ($filterSettings !== null) { if ($filterSettings !== null) {
@@ -293,67 +250,7 @@ class FilterService implements SingletonInterface
return [$dateFrom, $dateTo]; return [$dateFrom, $dateTo];
} }
/** public function getFiltersettingsFromSettings(array $settings): FilterSettings
* @param FilterSettings $filterSettings
* @param int $conceptUid
*
* @return FilterSettings
*/
public function forceConceptUid(FilterSettings $filterSettings, $conceptUid = null)
{
if ($conceptUid) {
$conceptUids = $filterSettings->getConceptUids();
if (!\in_array($conceptUid, $conceptUids, false)) {
$conceptUids[] = (int) $conceptUid;
$filterSettings->setConceptUids($conceptUids);
}
}
return $filterSettings;
}
/**
* Converts destination type and destination id combo
* from searchbar into appropriate filter setting for
* further processing
*
* @param FilterSettings $filterSettings
*/
public function convertDestinationData(FilterSettings $filterSettings)
{
if ($filterSettings->getDestinationUid() === 0) {
return;
}
$destinationUid = $filterSettings->getDestinationUid();
$destinationType = $filterSettings->getDestinationType();
switch ($destinationType)
{
case FilterSettings::DESTINATION_TYPE_COUNTRY:
$filterSettings->setCountryUids([$destinationUid]);
break;
case FilterSettings::DESTINATION_TYPE_REGION:
$filterSettings->setRegionUids([$destinationUid]);
break;
case FilterSettings::DESTINATION_TYPE_CITY:
$filterSettings->setCityUids([$destinationUid]);
break;
case FilterSettings::DESTINATION_TYPE_HOTEL:
$filterSettings->setHotelUid($destinationUid);
break;
case FilterSettings::DESTINATION_TYPE_PRODUCT:
$filterSettings->setProductUid($destinationUid);
break;
}
}
/**
* @param array $settings
* @return FilterSettings
*/
public function getFiltersettingsFromSettings(array $settings)
{ {
$filterSettings = new FilterSettings(); $filterSettings = new FilterSettings();
if (isset($settings['regionUid']) && (int) $settings['regionUid'] > 0) { if (isset($settings['regionUid']) && (int) $settings['regionUid'] > 0) {
@@ -384,6 +281,7 @@ class FilterService implements SingletonInterface
$hotelTypes = GeneralUtility::trimExplode(',', $settings['hotelTypes'], true); $hotelTypes = GeneralUtility::trimExplode(',', $settings['hotelTypes'], true);
$filterSettings->setHotelTypes($hotelTypes); $filterSettings->setHotelTypes($hotelTypes);
} }
return $filterSettings; return $filterSettings;
} }
} }
@@ -38,9 +38,6 @@ class FilterSettingsEncoder
protected static function getMapping() protected static function getMapping()
{ {
return [ return [
[ 'destinationName', 'string' ],
[ 'destinationUid', 'string' ],
[ 'destinationType', 'string' ],
[ 'countryUids', 'array' ], [ 'countryUids', 'array' ],
[ 'regionUids', 'array' ], [ 'regionUids', 'array' ],
[ 'cityUids', 'array' ], [ 'cityUids', 'array' ],
@@ -84,7 +81,7 @@ class FilterSettingsEncoder
$values = explode('|', $data); $values = explode('|', $data);
foreach (static::getMapping() as $index => $property) foreach (static::getMapping() as $index => $property)
{ {
list($name, $type) = $property; [$name, $type] = $property;
$value = $values[$index]; $value = $values[$index];
if ($type === 'array') { if ($type === 'array') {
$value = GeneralUtility::trimExplode(';', $value, true); $value = GeneralUtility::trimExplode(';', $value, true);
@@ -26,7 +26,7 @@ namespace EP\EpProducts\Service;
* This copyright notice MUST APPEAR in all copies of the script! * This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/ ***************************************************************/
use EP\EpProducts\Domain\Model\FilterSettings; use EP\EpProducts\Domain\Model\FilterOptions;
use EP\EpProducts\Traits\DbConnectionTrait; use EP\EpProducts\Traits\DbConnectionTrait;
use TYPO3\CMS\Backend\Utility\BackendUtility; use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\SingletonInterface; use TYPO3\CMS\Core\SingletonInterface;
@@ -75,7 +75,7 @@ class LogService implements SingletonInterface
$searchParams = $data->searchParams; $searchParams = $data->searchParams;
$priceRange = null; $priceRange = null;
if ($searchParams->priceRange) { if ($searchParams->priceRange) {
list ($priceFrom, $priceTo) = FilterSettings::$priceRanges[$searchParams->priceRange]; list ($priceFrom, $priceTo) = FilterOptions::$priceRangeOptions[$searchParams->priceRange];
$priceRange = sprintf('%s - %s', $priceFrom, $priceTo); $priceRange = sprintf('%s - %s', $priceFrom, $priceTo);
} }
$pageTitle = ''; $pageTitle = '';
@@ -38,17 +38,12 @@ trait RequestArgumentTypeConversionTrait
/** /**
* Converts array to searchparams object * Converts array to searchparams object
* @param array $forcedConceptUids
*/ */
public function processSearchParamsArgument(array $forcedConceptUids = []) public function processSearchParamsArgument()
{ {
if ($this->request->hasArgument('searchParams')) { if ($this->request->hasArgument('searchParams')) {
$searchParamsObject = SearchParams::fromRequest($this->request); $searchParamsObject = SearchParams::fromRequest($this->request);
if ($forcedConceptUids) {
$searchParamsObject->setConceptUids($forcedConceptUids);
}
$this->request->setArgument('searchParams', $searchParamsObject); $this->request->setArgument('searchParams', $searchParamsObject);
} }
} }
@@ -75,8 +70,6 @@ trait RequestArgumentTypeConversionTrait
array_push($conceptUids, ...$forcedConceptUids); array_push($conceptUids, ...$forcedConceptUids);
$filterSettingsObject->setConceptUids($conceptUids); $filterSettingsObject->setConceptUids($conceptUids);
} }
$filterService->convertDestinationData($filterSettingsObject);
} }
$this->request->setArgument('filterSettings', $filterSettingsObject); $this->request->setArgument('filterSettings', $filterSettingsObject);
@@ -24,6 +24,9 @@ class TypeConversionUtility
return static::convertType($item); return static::convertType($item);
}, $value); }, $value);
} }
if (empty($value)) {
return null;
}
return $value; return $value;
} }
} }
@@ -158,10 +158,6 @@ tx_epproducts_ajax_json {
AjaxSearchbar { AjaxSearchbar {
1 = update 1 = update
} }
AjaxFilterpanel {
1 = panel
2 = autocomplete
}
AjaxTable { AjaxTable {
1 = pricetable 1 = pricetable
2 = pricetableHtml 2 = pricetableHtml
@@ -371,7 +371,6 @@ $iconRegistry->registerIcon(
[ [
'AjaxSearch' => 'searchresult', 'AjaxSearch' => 'searchresult',
'AjaxDate' => 'dates', 'AjaxDate' => 'dates',
'AjaxFilterpanel' => 'panel,autocomplete',
'AjaxSearchbar' => 'update', 'AjaxSearchbar' => 'update',
'AjaxContingent' => 'list,rooms', 'AjaxContingent' => 'list,rooms',
'AjaxTable' => 'pricetable,pricetableHtml,eventPricetable', 'AjaxTable' => 'pricetable,pricetableHtml,eventPricetable',
@@ -382,7 +381,6 @@ $iconRegistry->registerIcon(
[ [
'AjaxSearch' => 'searchresult', 'AjaxSearch' => 'searchresult',
'AjaxDate' => 'dates', 'AjaxDate' => 'dates',
'AjaxFilterpanel' => 'panel,autocomplete',
'AjaxSearchbar' => 'update', 'AjaxSearchbar' => 'update',
'AjaxContingent' => 'list,rooms', 'AjaxContingent' => 'list,rooms',
'AjaxTable' => 'pricetable,pricetableHtml,eventPricetable', 'AjaxTable' => 'pricetable,pricetableHtml,eventPricetable',
@@ -28,8 +28,7 @@ import osmMap from './components/osm-map'
import productDetail from './components/product-detail' import productDetail from './components/product-detail'
import daytripDateSelect from './components/daytrip-date-select' import daytripDateSelect from './components/daytrip-date-select'
import searchBar from './components/search-bar' import searchBar from './components/search-bar'
import searchResult from './components/search-result' import search from './components/search'
import filterPanel from './components/filter-panel'
import defaultFilterSettings from './_filtersettings' import defaultFilterSettings from './_filtersettings'
import defaultFilterOptions from './_filteroptions' import defaultFilterOptions from './_filteroptions'
@@ -63,8 +62,7 @@ document.addEventListener('DOMContentLoaded', () => {
Alpine.data('productDetail', productDetail) Alpine.data('productDetail', productDetail)
Alpine.data('daytripDateSelect', daytripDateSelect) Alpine.data('daytripDateSelect', daytripDateSelect)
Alpine.data('searchBar', searchBar) Alpine.data('searchBar', searchBar)
Alpine.data('searchResult', searchResult) Alpine.data('search', search)
Alpine.data('filterPanel', filterPanel)
Alpine.start() Alpine.start()
Glightbox({ Glightbox({
@@ -1,7 +1,4 @@
export default { export default {
destinationName: '',
destinationUid: 0,
destinationType: '',
productUid: 0, productUid: 0,
hotelUid: 0, hotelUid: 0,
countryUids: [], countryUids: [],
@@ -10,7 +7,7 @@ export default {
conceptUids: [], conceptUids: [],
dateFrom: null, dateFrom: null,
dateTo: null, dateTo: null,
priceRange: 0, priceRanges: [],
nights: 0, nights: 0,
pax: 1, pax: 1,
hotelTypes: [], hotelTypes: [],
@@ -2,4 +2,4 @@ require('font-awesome/css/font-awesome.min.css')
require('cookieconsent/build/cookieconsent.min.css') require('cookieconsent/build/cookieconsent.min.css')
require('glightbox/dist/css/glightbox.css') require('glightbox/dist/css/glightbox.css')
require('leaflet/dist/leaflet.css') require('leaflet/dist/leaflet.css')
require('flatpickr/dist/flatpickr.css')
@@ -53,9 +53,11 @@ export default props => ({
this.filterOptions.destinations = filterOptions.destinations this.filterOptions.destinations = filterOptions.destinations
if (filterOptions.dateFrom !== null) { if (filterOptions.dateFrom !== null) {
this.filterOptions.selectableRangeFrom = filterOptions.dateFrom this.filterOptions.selectableRangeFrom = filterOptions.dateFrom
this.picker.set('minDate', new Date(filterOptions.dateFrom))
} }
if (filterOptions.dateTo !== null) { if (filterOptions.dateTo !== null) {
this.filterOptions.selectableRangeTo = filterOptions.dateTo this.filterOptions.selectableRangeTo = filterOptions.dateTo
this.picker.set('maxDate', new Date(filterOptions.dateTo))
} }
}, },
updateSearchParam(param, value) { updateSearchParam(param, value) {
@@ -75,24 +77,18 @@ export default props => ({
}, },
selectDestination(uid, name, type) { selectDestination(uid, name, type) {
this.destinationName = name this.destinationName = name
this.destinationSelected = true this.destinationSelected = type !== 'concept'
this.conceptSelected = false this.conceptSelected = type === 'concept'
this.searchParams.destinationUid = uid this.searchParams.destinationUid = uid
this.searchParams.destinationType = type this.searchParams.destinationType = type
this.load() this.load()
}, },
selectConcept(uid, name) {
this.destinationName = name
this.destinationSelected = false
this.conceptSelected = true
this.searchParams.destinationUid = uid
this.searchParams.destinationType = 'concept'
this.load()
},
resetDestination() { resetDestination() {
this.destinationName = 'beliebig' this.destinationName = 'beliebig'
this.destinationSelected = false this.destinationSelected = false
this.conceptSelected = false this.conceptSelected = false
this.searchParams.destinationUid = null
this.searchParams.destinationType = null
this.load() this.load()
}, },
initDatePicker() { initDatePicker() {
@@ -106,14 +102,14 @@ export default props => ({
} }
} }
}) })
let minDate = this.filterOptions.minDate ? new Date(this.filterOptions.minDate) : new Date let minDate = this.filterOptions.dateFrom ? new Date(this.filterOptions.dateFrom) : new Date
this.picker.set('minDate', minDate) this.picker.set('minDate', minDate)
if (this.filterOptions.maxDate) { if (this.filterOptions.dateTo) {
this.picker.set('maxDate', new Date(this.filterOptions.maxDate)) this.picker.set('maxDate', new Date(this.filterOptions.dateTo))
} }
}, },
priceRangeLabel() { priceRangeLabel() {
if (this.searchParams.priceRange === 0) { if (null === this.searchParams.priceRange) {
return 'beliebig' return 'beliebig'
} }
let range = this.filterOptions.priceRanges[this.searchParams.priceRange] let range = this.filterOptions.priceRanges[this.searchParams.priceRange]
@@ -1,27 +0,0 @@
import axios from 'axios'
export default props => ({
loading: false,
uri: props.uri,
filterSettings: props.initialFilterSettings,
init() {
this.load()
},
onFilterSettingsUpdated(e) {
this.filterSettings = e.detail
this.load()
},
load() {
this.loading = true
let data = new URLSearchParams({
'tx_epproducts_ajax[filterSettings]': JSON.stringify(this.filterSettings)
})
axios.post(this.uri, data)
.then(response => {
this.$refs.results.innerHTML = response.data
})
.finally(() => {
this.loading = false
})
}
})
@@ -14,15 +14,16 @@ export default props => ({
filterCount: 0, filterCount: 0,
dateRangeSelected: { from: null, to: null }, dateRangeSelected: { from: null, to: null },
dateRangeLabel: null, dateRangeLabel: null,
contentWrapper: null,
init() { init() {
this.contentWrapper = this.$refs.container
this.updateFilterCount() this.updateFilterCount()
this.load() this.load()
this.$watch('filterSettings', () => { },
this.$dispatch('filter-settings-updated', this.filterSettings) refresh() {
this.updateFilterCount() this.updateFilterCount()
this.updateDateRangeLabel() this.updateDateRangeLabel()
this.load() this.load()
})
}, },
load() { load() {
this.loading = true this.loading = true
@@ -31,7 +32,7 @@ export default props => ({
}) })
axios.post(this.uri, data) axios.post(this.uri, data)
.then(response => { .then(response => {
this.$refs.panel.innerHTML = response.data this.contentWrapper.innerHTML = response.data
}) })
.finally(() => { .finally(() => {
this.loading = false this.loading = false
@@ -45,6 +46,7 @@ export default props => ({
}, },
reset() { reset() {
this.filterSettings = {...this.defaultFilterSettings} this.filterSettings = {...this.defaultFilterSettings}
this.dateRangeSelected = { from: null, to: null }
this.dateRangeLabel = null this.dateRangeLabel = null
}, },
initDatePicker(minDate, maxDate) { initDatePicker(minDate, maxDate) {
@@ -61,7 +63,6 @@ export default props => ({
dateFrom: flatpickr.formatDate(selectedDates[0], 'Y-m-d'), dateFrom: flatpickr.formatDate(selectedDates[0], 'Y-m-d'),
dateTo: flatpickr.formatDate(selectedDates[1], 'Y-m-d'), dateTo: flatpickr.formatDate(selectedDates[1], 'Y-m-d'),
}) })
this.updateDateRangeLabel()
this.show = null this.show = null
} }
} }
@@ -74,13 +75,12 @@ export default props => ({
selectDateRangePreset(preset) { selectDateRangePreset(preset) {
let dateFrom = new Date(preset.dateFrom) let dateFrom = new Date(preset.dateFrom)
let dateTo = new Date(preset.dateTo) let dateTo = new Date(preset.dateTo)
this.picker.setDate([ dateFrom, dateTo ])
this.dateRangeSelected = { from: dateFrom, to: dateTo } this.dateRangeSelected = { from: dateFrom, to: dateTo }
this.updateFilterSettings({ this.updateFilterSettings({
dateFrom: flatpickr.formatDate(dateFrom, 'Y-m-d'), dateFrom: flatpickr.formatDate(dateFrom, 'Y-m-d'),
dateTo: flatpickr.formatDate(dateTo, 'Y-m-d'), dateTo: flatpickr.formatDate(dateTo, 'Y-m-d'),
}) })
this.updateDateRangeLabel()
this.picker.setDate([ dateFrom, dateTo ])
this.show = null this.show = null
}, },
resetDateRange() { resetDateRange() {
@@ -89,40 +89,8 @@ export default props => ({
dateFrom: null, dateFrom: null,
dateTo: null, dateTo: null,
}) })
this.updateDateRangeLabel()
this.show = null this.show = null
}, },
toggleFilterValue(name, value, multiple = false) {
let updates = {}
let newValue = this.filterSettings[name]
if (!multiple) {
newValue = newValue === value ? null : value
} else {
let index = newValue.indexOf(value)
if (index !== -1) {
newValue.splice(index, 1)
} else {
newValue.push(value)
}
}
updates[name] = newValue
this.updateFilterSettings(updates)
},
toggleDestinationOrConcept(uid, name, type) {
let values = { destinationUid: null, destinationName: null, destinationType: null }
if ('concept' === type) {
values = {...values, conceptUids: []}
}
let currentUid = this.filterSettings.destinationUid
let currentType = this.filterSettings.destinationType
if (currentUid !== uid || currentType !== type) {
values = { destinationUid: uid, destinationName: name, destinationType: type }
if ('concept' === type) {
values = {...values, conceptUids: [uid]}
}
}
this.updateFilterSettings(values)
},
updateDateRangeLabel() { updateDateRangeLabel() {
let dateRangeLabel = '' let dateRangeLabel = ''
if (null !== this.dateRangeSelected.from && null !== this.dateRangeSelected.to) { if (null !== this.dateRangeSelected.from && null !== this.dateRangeSelected.to) {
@@ -134,8 +102,8 @@ export default props => ({
}, },
updateFilterCount() { updateFilterCount() {
let count = 0 let count = 0
if (this.filterSettings.destinationUid) count++ if (this.filterSettings.dateFrom !== null) count++
if (this.filterSettings.destinationName) count++ if (this.filterSettings.dateTo !== null) count++
if (this.filterSettings.countryUids.length > 0) count++ if (this.filterSettings.countryUids.length > 0) count++
if (this.filterSettings.regionUids.length > 0) count++ if (this.filterSettings.regionUids.length > 0) count++
if (this.filterSettings.cityUids.length > 0) count++ if (this.filterSettings.cityUids.length > 0) count++
@@ -143,9 +111,7 @@ export default props => ({
if (this.filterSettings.hotelTypes.length > 0) count++ if (this.filterSettings.hotelTypes.length > 0) count++
if (this.filterSettings.boardTypes.length > 0) count++ if (this.filterSettings.boardTypes.length > 0) count++
if (this.filterSettings.roomTypes.length > 0) count++ if (this.filterSettings.roomTypes.length > 0) count++
if (this.filterSettings.dateFrom !== null) count++ if (this.filterSettings.priceRanges.length > 0) count++
if (this.filterSettings.dateTo !== null) count++
if (this.filterSettings.priceRange > 0) count++
if (this.filterSettings.bus) count++ if (this.filterSettings.bus) count++
if (this.filterSettings.pax > 2) count++ if (this.filterSettings.pax > 2) count++
this.filterCount = count this.filterCount = count
@@ -1,5 +1,10 @@
@import "~flatpickr/dist/flatpickr.css";
.flatpickr-calendar { .flatpickr-calendar {
box-shadow: none; box-shadow: none;
&.inline {
border: none !important;
}
} }
.daterange { .daterange {
@@ -1,223 +0,0 @@
<html data-namespace-typo3-fluid="true" lang="en"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://typo3.org/ns/fluid/ViewHelpers"
xmlns:ep="http://typo3.org/ns/EP/EpTheme/ViewHelpers">
<form class="border rounded p-4">
<div x-data="collapse" x-init='initDatePicker(
"<f:format.raw>{filterOptions.dateFrom -> f:format.date(format: 'Y-m-d')}</f:format.raw>",
"<f:format.raw>{filterOptions.dateTo -> f:format.date(format: 'Y-m-d')}</f:format.raw>"
)'>
<label x-bind="trigger">
Zeitraum
</label>
<div x-bind="container" class="relative">
<input type="text"
class="form-control cursor-pointer"
placeholder="von... bis"
x-bind:value="dateRangeLabel"
x-bind:class="loading && 'cursor-not-allowed'"
x-on:click.prevent="show = show === 'dateRange' ? null : 'dateRange'"/>
<div class="absolute top-100 left-0 z-20 bg-white p-4 shadow-md" x-show="show === 'dateRange'" x-transition x-cloak>
<template x-if="datePresets">
<div class="flex items-center justify-between pb-2">
<template x-for="preset in datePresets">
<a class="daterange__preset" href="#"
x-on:click.prevent="selectDateRangePreset(preset)">
<i class="fa fa-calendar"></i> <span x-text="preset.label"></span>
</a>
</template>
</div>
</template>
<input x-ref="picker" type="hidden">
<div class="flex items-center justify-between pt-2">
<button class="daterange__button focus:outline-none" x-on:click.prevent="show = null">
<i class="fa fa-times"></i> schließen
</button>
<button class="daterange__button focus:outline-none" x-on:click.prevent="resetDateRange()">
<i class="fa fa-trash-o"></i> zurücksetzen
</button>
</div>
</div>
</div>
</div>
<div x-data="collapse">
<label x-bind="trigger" class="cursor-pointer">
Reiseziel
</label>
<div x-bind="container" x-transition>
<ul class="border rounded shadow-md mb-0">
<f:for each="{filterOptions.destinations}" as="destination">
<li>
<a href="#"
x-on:click.prevent="toggleDestinationOrConcept(<f:format.raw>{destination.country.uid}, '{destination.country.label}', 'country'</f:format.raw>)"
class="{f:if(condition: '{filterSettings.destinationType} == "country" && {filterSettings.destinationUid} == {destination.country.uid}', then: 'underline')}">
{destination.country.label}
</a>
<f:if condition="{destination.regions -> f:count()}">
<ul>
<f:for each="{destination.regions}" as="region">
<li>
<a href="#"
x-on:click.prevent="toggleDestinationOrConcept(<f:format.raw>{region.uid}, '{region.label}', 'region'</f:format.raw>)"
class="{f:if(condition: '{filterSettings.destinationType} == "region" && {filterSettings.destinationUid} == {region.uid}', then: 'underline')}">
{region.label}
</a>
</li>
</f:for>
</ul>
</f:if>
</li>
</f:for>
</ul>
</div>
</div>
<div x-data="collapse">
<label x-bind="trigger">
Reisekonzepte
</label>
<div x-bind="container" x-transition>
<ul class="border rounded shadow-md mb-0">
<f:for each="{filterOptions.concepts}" as="concept">
<li>
<a href="#"
x-on:click.prevent="toggleDestinationOrConcept(<f:format.raw>{concept.uid}, '{concept.name}', 'concept'</f:format.raw>)"
class="{f:if(condition: '{filterSettings.destinationType} == "concept" && {filterSettings.destinationUid} == {concept.uid}', then: 'underline')}">
{concept.name}
</a>
</li>
</f:for>
</ul>
</div>
</div>
<div x-data="collapse">
<label x-bind="trigger">
Preis
</label>
<div x-bind="container" x-transition>
<ul class="border rounded shadow-md mb-0">
<f:for each="{filterOptions.priceRanges}" as="range">
<li>
<a href="#"
x-on:click.prevent="toggleFilterValue('priceRange', <f:format.raw>{range.uid}</f:format.raw>)"
class="{f:if(condition: '{filterSettings.priceRange} == {range.uid}', then: 'underline')}">
{ep:priceRange(min: range.min, max: range.max)}
</a>
</li>
</f:for>
</ul>
</div>
</div>
<div x-data="collapse">
<label x-bind="trigger">
Personen
</label>
<div x-bind="container" x-transition>
<ul class="h-48 overflow-y-scroll border rounded shadow-md mb-0">
<f:for each="{filterOptions.pax}" as="option">
<li>
<a href="#"
x-on:click.prevent="toggleFilterValue('pax', <f:format.raw>{option}</f:format.raw>)">
{option}
</a>
</li>
</f:for>
<li><a href="{f:uri.typolink(parameter: settings.groupsPageUid)}">&gt; 20</a></li>
</ul>
</div>
</div>
<f:variable name="hotelTypes" value="{
1: 'Ferienwohnung',
2: 'Sportclub',
3: 'Appartment',
4: 'Gästehaus',
5: 'Pension'
}"/>
<div x-data="collapse">
<label x-bind="trigger">
Unterkunft
</label>
<div x-bind="container" x-transition>
<ul class="border rounded shadow-md mb-0">
<f:for each="{filterOptions.hotelTypes}" as="type">
<li>
<a href="#"
x-on:click.prevent="toggleFilterValue('hotelTypes', <f:format.raw>{type}</f:format.raw>, true)">
{hotelTypes.{type}}
</a>
</li>
</f:for>
</ul>
</div>
</div>
<div x-data="collapse">
<label x-bind="trigger">
Verpflegung
</label>
<div x-bind="container" x-transition>
<ul class="border rounded shadow-md mb-0">
<f:for each="{filterOptions.boardTypes}" as="type">
<li>
<a href="#"
x-on:click.prevent="toggleFilterValue('boardTypes', <f:format.raw>{type.uid}</f:format.raw>, true)">
{type.label}
</a>
</li>
</f:for>
</ul>
</div>
</div>
<f:variable name="roomTypes" value="{
1: 'EZ',
2: 'DZ / 2er',
3: '3er',
4: '4er',
5: 'ab 5 Pers.',
6: 'mit anderen'
}"/>
<div x-data="collapse">
<label x-bind="trigger">
Zimmerarten/Apart.
</label>
<div x-bind="container" x-transition>
<ul class="border rounded shadow-md mb-0">
<f:for each="{filterOptions.roomTypes}" as="type">
<li>
<a href="#"
x-on:click.prevent="toggleFilterValue('roomTypes', <f:format.raw>{type}</f:format.raw>, true)">
{roomTypes.{type}}
</a>
</li>
</f:for>
</ul>
</div>
</div>
<f:if condition="{filterOptions.busAvailable}">
<div x-data="collapse">
<label x-bind="trigger">
Anreise
</label>
<div x-bind="container" x-transition>
<ul class="border rounded shadow-md mb-0">
<li>
<a href="#"
x-on:click.prevent="toggleFilterValue('bus', true)"
class="{f:if(condition: filterSettings.bus, then: 'underline')}">
Busanreise
</a>
</li>
</ul>
</div>
</div>
</f:if>
</form>
</html>
@@ -3,51 +3,83 @@
xmlns:f="http://typo3.org/ns/fluid/ViewHelpers" xmlns:f="http://typo3.org/ns/fluid/ViewHelpers"
xmlns:ep="http://typo3.org/ns/EP/EpTheme/ViewHelpers"> xmlns:ep="http://typo3.org/ns/EP/EpTheme/ViewHelpers">
<div class="searchresult__info alert alert-info"> <f:layout name="Default"/>
Deine Suche
<f:if condition="{meta.organic}"> <f:section name="Main">
<f:then> <div class="row" x-ref="container">
<f:if condition="{meta.destinationName}"> <div class="col-md-3 relative" x-data="collapse(false, 991)">
nach <strong>{meta.destinationName}</strong> <p class="lg:hidden">
</f:if> <button class="button button--full button--small" role="button" x-bind="trigger">
<f:if condition="{meta.dateRange}"> <i class="fa fa-toggle-on"></i> Filter ein-/ausblenden
<strong>{meta.dateRange}</strong> </button>
</f:if> </p>
ergab {meta.total} Treffer <div class="lg:block"
</f:then> x-bind="container"
<f:else> x-transition
ergab leider keine genauen Treffer. Bitte benutze die Filter. x-cloak
</f:else> >
</f:if> <h3>Filter</h3>
</div> <button class="button button--sidebar button--small"
<div class="searchresult__content"> x-show="filterCount > 0"
<f:for each="{searchResult}" as="section"> x-cloak
<div id="concept-{section.concept.uid}"> x-on:click.prevent="reset">
<h2 class="headline headline--secondary headline--underlined"> Filter zurücksetzen
{section.concept.name} </button>
</h2> <f:render section="FilterPanel" arguments="{_all}"/>
<div class="searchresult-section searchresult-section--{section.concept.code}"> </div>
<f:for each="{section.items}" as="item"> </div>
<div class="teaserbox teaserbox--landscape teaserbox--searchresult searchresult-item"> <div class="col-md-9 relative">
<div class="teaserbox__image"> <f:render section="SearchResult" arguments="{_all}"/>
<img src="{item.country.flag}" class="teaserbox__flag" alt="{item.country.name}"/> </div>
<a href="{item.detailPageUri}" rel="nofollow" title="Details &amp; Buchen"> </div>
<picture class="searchresult-item__image"> </f:section>
<source media="(min-width: 1201px)" data-srcset="{item.hotel.images.resized.m.0.url} 260w"/>
<source media="(max-width: 1200px)" data-srcset="{item.hotel.images.resized.l.0.url} 280w"/> <f:section name="SearchResult">
<source media="(max-width: 768px)" data-srcset="{item.hotel.images.resized.s.0.url} 220w"/> <div class="searchresult__info alert alert-info">
<img class="scale lazyload" data-src="{item.hotel.images.resized.s[0].url}" Deine Suche
title="{item.hotel.images.resized.s.0.title}" <f:if condition="{meta.organic}">
alt="{item.hotel.images.resized.s.0.alt}"> <f:then>
</picture> <f:if condition="{meta.destinationName}">
</a> nach <strong>{meta.destinationName}</strong>
<div class="concept-badge concept-badge--{section.concept.code}" </f:if>
id="{section.concept.code}"> <f:if condition="{meta.dateRange}">
{section.concept.name} <strong>{meta.dateRange}</strong>
</f:if>
ergab {meta.total} Treffer
</f:then>
<f:else>
ergab leider keine genauen Treffer. Bitte benutze die Filter.
</f:else>
</f:if>
</div>
<div class="searchresult__content">
<f:for each="{searchResult}" as="section">
<div id="concept-{section.concept.uid}">
<h2 class="headline headline--secondary headline--underlined">
{section.concept.name}
</h2>
<div class="searchresult-section searchresult-section--{section.concept.code}">
<f:for each="{section.items}" as="item">
<div class="teaserbox teaserbox--landscape teaserbox--searchresult searchresult-item">
<div class="teaserbox__image">
<img src="{item.country.flag}" class="teaserbox__flag" alt="{item.country.name}"/>
<a href="{item.detailPageUri}" rel="nofollow" title="Details &amp; Buchen">
<picture class="searchresult-item__image">
<source media="(min-width: 1201px)" data-srcset="{item.hotel.images.resized.m.0.url} 260w"/>
<source media="(max-width: 1200px)" data-srcset="{item.hotel.images.resized.l.0.url} 280w"/>
<source media="(max-width: 768px)" data-srcset="{item.hotel.images.resized.s.0.url} 220w"/>
<img class="scale lazyload" data-src="{item.hotel.images.resized.s[0].url}"
title="{item.hotel.images.resized.s.0.title}"
alt="{item.hotel.images.resized.s.0.alt}">
</picture>
</a>
<div class="concept-badge concept-badge--{section.concept.code}"
id="{section.concept.code}">
{section.concept.name}
</div>
</div> </div>
</div> <div class="teaserbox__main">
<div class="teaserbox__main"> <div class="teaserbox__header">
<div class="teaserbox__header">
<span class="searchresult-item__headline"> <span class="searchresult-item__headline">
{item.region.name} {item.region.name}
<f:if condition="{section.concept.code} == 'evt'"> <f:if condition="{section.concept.code} == 'evt'">
@@ -58,85 +90,328 @@
</strong> </strong>
</f:if> </f:if>
</span> </span>
<span class="searchresult-item__subline"> <span class="searchresult-item__subline">
{item.hotel.name} {item.hotel.name}
</span> </span>
<f:if condition="{item.hotel.category}"> <f:if condition="{item.hotel.category}">
<span class="searchresult-item__category"> <span class="searchresult-item__category">
Kategorie {item.hotel.category} Kategorie {item.hotel.category}
</span> </span>
</f:if> </f:if>
</div>
<ul class="check-list searchresult-item__checklist">
<f:if condition="{item.hotel.fact}">
<li>{item.hotel.fact}</li>
</f:if>
<f:if condition="{item.product.fact}">
<li>{item.product.fact}</li>
</f:if>
<f:if condition="{item.region.feature}">
<f:then>
<li>{item.region.feature}</li>
</f:then>
<f:else>
<f:if condition="{item.region.fact}">
<li>{item.region.fact}</li>
</f:if>
</f:else>
</f:if>
</ul>
<ul class="check-list check-list--inverted searchresult-item__featurelist">
<li>Skipass</li>
<f:if condition="{item.board}">
<li>{item.board}</li>
</f:if>
<f:if condition="{item.product.feature}">
<li>{item.product.feature}</li>
</f:if>
</ul>
</div> </div>
<ul class="check-list searchresult-item__checklist"> <div class="teaserbox__aside">
<f:if condition="{item.hotel.fact}"> <div class="teaserbox__aside__top">
<li>{item.hotel.fact}</li>
</f:if>
<f:if condition="{item.product.fact}">
<li>{item.product.fact}</li>
</f:if>
<f:if condition="{item.region.feature}">
<f:then>
<li>{item.region.feature}</li>
</f:then>
<f:else>
<f:if condition="{item.region.fact}">
<li>{item.region.fact}</li>
</f:if>
</f:else>
</f:if>
</ul>
<ul class="check-list check-list--inverted searchresult-item__featurelist">
<li>Skipass</li>
<f:if condition="{item.board}">
<li>{item.board}</li>
</f:if>
<f:if condition="{item.product.feature}">
<li>{item.product.feature}</li>
</f:if>
</ul>
</div>
<div class="teaserbox__aside">
<div class="teaserbox__aside__top">
<span class="searchresult-item__label"> <span class="searchresult-item__label">
{f:if(condition: '{item.dates -> f:count()} > 1', then: 'Zeitraum', else: 'Termin')} {f:if(condition: '{item.dates -> f:count()} > 1', then: 'Zeitraum', else: 'Termin')}
</span> </span>
<span class="searchresult-item__daterange"> <span class="searchresult-item__daterange">
{ep:dateRange(dateFrom: item.minDateStart, dateTo: item.maxDateEnd, includeLabel: 0)} {ep:dateRange(dateFrom: item.minDateStart, dateTo: item.maxDateEnd, includeLabel: 0)}
</span> </span>
<f:if condition="{item.dates -> f:count()} > 0"> <f:if condition="{item.dates -> f:count()} > 0">
<f:if condition="{item.daytrip}"> <f:if condition="{item.daytrip}">
<f:else> <f:else>
<a href="{item.detailPageUri}{f:if(condition: referringPageUri, then: '?ref={referringPageUri -> f:format.urlencode()}')}" rel="nofollow" title="Details &amp; Buchen"> <a href="{item.detailPageUri}{f:if(condition: referringPageUri, then: '?ref={referringPageUri -> f:format.urlencode()}')}" rel="nofollow" title="Details &amp; Buchen">
<span class="searchresult-item__daterange"> <span class="searchresult-item__daterange">
<i class="fa fa-arrow-circle-right"></i> {item.dates -> f:count()} {f:if(condition: '{item.dates -> f:count()} > 1', then: 'Termine', else: 'Termin')} <i class="fa fa-arrow-circle-right"></i> {item.dates -> f:count()} {f:if(condition: '{item.dates -> f:count()} > 1', then: 'Termine', else: 'Termin')}
</span> </span>
</a> </a>
</f:else> </f:else>
</f:if>
</f:if> </f:if>
</f:if> <hr>
<hr> <span class="searchresult-item__label">
<span class="searchresult-item__label">
{f:if(condition: item.daytrip, then: 'Tageweise buchbar', else: '{ep:nightsOptions(options: item.nights, daytrip: item.daytrip)}')} {f:if(condition: item.daytrip, then: 'Tageweise buchbar', else: '{ep:nightsOptions(options: item.nights, daytrip: item.daytrip)}')}
</span> </span>
</div>
<div class="teaserbox__aside__bottom">
<span class="searchresult-item__price">ab {item.minPrice} €</span>
<a href="{item.detailPageUri}{f:if(condition: referringPageUri, then: '?ref={referringPageUri -> f:format.urlencode()}')}" class="button button--full searchresult-item__button"
rel="nofollow" title="Details">
Details{f:if(condition: item.hideBookingButton, else: ' &amp; Buchen')}
</a>
</div>
</div> </div>
<div class="teaserbox__aside__bottom"> <a href="#" class="teaserbox-watchlist" x-data="watchlistToggle('{item.key}')" x-bind="trigger">
<span class="searchresult-item__price">ab {item.minPrice} €</span> <span data-microtip-position="top" x-bind:aria-label="label" role="tooltip">
<a href="{item.detailPageUri}{f:if(condition: referringPageUri, then: '?ref={referringPageUri -> f:format.urlencode()}')}" class="button button--full searchresult-item__button" <i class="fa fa-plus-circle" x-bind:class="icon"></i>
rel="nofollow" title="Details"> </span>
Details{f:if(condition: item.hideBookingButton, else: ' &amp; Buchen')} </a>
</a>
</div>
</div> </div>
<a href="#" class="teaserbox-watchlist" x-data="watchlistToggle('{item.key}')" x-bind="trigger"> </f:for>
<span data-microtip-position="top" x-bind:aria-label="label" role="tooltip"> </div>
<i class="fa fa-plus-circle" x-bind:class="icon"></i> </div>
</span> </f:for>
</a> </div>
</f:section>
<f:section name="FilterPanel">
<div class="border rounded p-4">
<div class="mb-2 pb-2 border-b" x-data="collapse(<f:if condition="{filterSettings.dateFrom} || {filterSettings.dateTo}">false</f:if>)"
x-init='initDatePicker(
<f:format.raw>
"{filterOptions.dateFrom -> f:format.date(format: 'Y-m-d')}",
"{filterOptions.dateTo -> f:format.date(format: 'Y-m-d')}"
</f:format.raw>
)'>
<label x-bind="trigger">
Zeitraum
</label>
<div x-bind="container" class="relative">
<input type="text"
class="cursor-pointer focus:outline-none"
placeholder="von... bis"
x-bind:value="dateRangeLabel"
x-bind:class="loading && 'cursor-not-allowed'"
x-on:click.prevent="show = show === 'dateRange' ? null : 'dateRange'"/>
<div class="absolute top-100 left-0 z-20 bg-white p-4 shadow-md" x-show="show === 'dateRange'" x-transition x-cloak>
<template x-if="datePresets">
<div class="flex items-center justify-between pb-2">
<template x-for="preset in datePresets">
<a class="daterange__preset" href="#"
x-on:click.prevent="selectDateRangePreset(preset)">
<i class="fa fa-calendar"></i> <span x-text="preset.label"></span>
</a>
</template>
</div> </div>
</f:for> </template>
<input x-ref="picker" type="hidden">
<div class="flex items-center justify-between pt-2">
<button class="daterange__button focus:outline-none" x-on:click.prevent="show = null">
<i class="fa fa-times"></i> schließen
</button>
<button class="daterange__button focus:outline-none" x-on:click.prevent="resetDateRange()">
<i class="fa fa-trash-o"></i> zurücksetzen
</button>
</div>
</div> </div>
</div> </div>
</f:for> </div>
</div>
<div class="mb-2 pb-2 border-b" x-data="collapse(<f:if condition="{filterSettings.countryUids -> f:count()} || {filterSettings.regionUids -> f:count()}">false</f:if>)">
<label x-bind="trigger" class="cursor-pointer">
Reiseziel
</label>
<div x-bind="container" x-transition>
<ul class="mb-0 p-0">
<f:for each="{filterOptions.destinations}" as="destination">
<li>
<input type="checkbox"
class="mr-2"
x-on:change="refresh"
x-model.number="filterSettings.countryUids"
value="{destination.country.uid}">
{destination.country.label}
<f:if condition="{destination.regions -> f:count()}">
<ul>
<f:for each="{destination.regions}" as="region">
<li>
<input type="checkbox"
class="mr-2"
x-on:change="refresh"
x-model.number="filterSettings.regionUids"
value="{region.uid}">
{region.label}
</li>
</f:for>
</ul>
</f:if>
</li>
</f:for>
</ul>
</div>
</div>
<div class="mb-2 pb-2 border-b" x-data="collapse(<f:if condition="{filterSettings.conceptUids -> f:count()}">false</f:if>)">
<label x-bind="trigger">
Reisekonzepte
</label>
<div x-bind="container" x-transition>
<ul class="mb-0 p-0">
<f:for each="{filterOptions.concepts}" as="concept">
<li>
<input type="checkbox"
class="mr-2"
x-on:change="refresh"
x-model.number="filterSettings.conceptUids"
value="{concept.uid}">
{concept.name}
</li>
</f:for>
</ul>
</div>
</div>
<div class="mb-2 pb-2 border-b" x-data="collapse(<f:if condition="{filterSettings.priceRanges -> f:count()}">false</f:if>)">
<label x-bind="trigger">
Preis
</label>
<div x-bind="container" x-transition>
<ul class="mb-0 p-0">
<f:for each="{filterOptions.priceRanges}" as="range">
<li>
<label>
<input type="checkbox"
class="mr-2"
x-on:change="refresh"
x-model.number="filterSettings.priceRanges"
value="<f:format.raw>{range.uid}</f:format.raw>">
{ep:priceRange(min: range.min, max: range.max)}
</label>
</li>
</f:for>
</ul>
</div>
</div>
<div class="mb-2 pb-2 border-b" x-data="collapse(<f:if condition="{filterSettings.pax} > 1">false</f:if>)">
<label x-bind="trigger">
Personen
</label>
<div x-bind="container" x-transition>
<div class="border rounded">
<select x-model="filterSettings.pax" x-on:change="refresh">
<f:for each="{filterOptions.pax}" as="option">
<option value="<f:format.raw>{option}</f:format.raw>">
<f:format.raw>{option}</f:format.raw>
</option>
</f:for>
</select>
</div>
</div>
</div>
<f:variable name="hotelTypes" value="{
1: 'Ferienwohnung',
2: 'Sportclub',
3: 'Appartment',
4: 'Gästehaus',
5: 'Pension'
}"/>
<div class="mb-2 pb-2 border-b" x-data="collapse(<f:if condition="{filterSettings.hotelTypes -> f:count()}">false</f:if>)">
<label x-bind="trigger">
Unterkunft
</label>
<div x-bind="container" x-transition>
<ul class="mb-0 p-0">
<f:for each="{filterOptions.hotelTypes}" as="type">
<li>
<label>
<input type="checkbox"
class="mr-2"
x-on:change="refresh"
x-model.number="filterSettings.hotelTypes"
value="<f:format.raw>{type}</f:format.raw>">
{hotelTypes.{type}}
</label>
</li>
</f:for>
</ul>
</div>
</div>
<div class="mb-2 pb-2 border-b" x-data="collapse(<f:if condition="{filterSettings.boardTypes -> f:count()}">false</f:if>)">
<label x-bind="trigger">
Verpflegung
</label>
<div x-bind="container" x-transition>
<ul class="mb-0 p-0">
<f:for each="{filterOptions.boardTypes}" as="type">
<f:if condition="{type.label}">
<li>
<label>
<input type="checkbox"
class="mr-2"
x-on:change="refresh"
x-model.number="filterSettings.boardTypes"
value="<f:format.raw>{type.uid}</f:format.raw>">
{type.label}
</label>
</li>
</f:if>
</f:for>
</ul>
</div>
</div>
<f:variable name="roomTypes" value="{
1: 'EZ',
2: 'DZ / 2er',
3: '3er',
4: '4er',
5: 'ab 5 Pers.',
6: 'mit anderen'
}"/>
<div class="mb-2 pb-2 border-b" x-data="collapse(<f:if condition="{filterSettings.roomTypes -> f:count()}">false</f:if>)">
<label x-bind="trigger">
Zimmerarten/Apart.
</label>
<div x-bind="container" x-transition>
<ul class="mb-0 p-0 p-0">
<f:for each="{filterOptions.roomTypes}" as="type">
<li>
<label>
<input type="checkbox"
class="mr-2"
x-on:change="refresh"
x-model.number="filterSettings.roomTypes"
value="<f:format.raw>{type}</f:format.raw>">
{roomTypes.{type}}
</label>
</li>
</f:for>
</ul>
</div>
</div>
<f:if condition="{filterOptions.busAvailable}">
<div x-data="collapse(<f:if condition="{filterSettings.bus}">false</f:if>)">
<label x-bind="trigger">
Anreise
</label>
<div x-bind="container" x-transition>
<ul class="mb-0 p-0">
<li>
<label>
<input type="checkbox"
class="mr-2"
x-on:change="refresh"
x-model="filterSettings.bus">
Busanreise
</label>
</li>
</ul>
</div>
</div>
</f:if>
</div>
</f:section>
</html> </html>
@@ -475,7 +475,7 @@
<span x-show="addBus"> <span x-show="addBus">
<i class="fa fa-arrow-circle-right"></i> Preise für Eigenanreise anzeigen <i class="fa fa-arrow-circle-right"></i> Preise für Eigenanreise anzeigen
</span> </span>
<span x-show="! addBus"> <span x-show="! addBus">
<i class="fa fa-arrow-circle-right"></i> Preise für Busanreise anzeigen <i class="fa fa-arrow-circle-right"></i> Preise für Busanreise anzeigen
</span> </span>
</a> </a>
@@ -495,9 +495,9 @@
<f:section name="DaytripDateSelect"> <f:section name="DaytripDateSelect">
<div class="date-select-wrapper" x-data='daytripDateSelect({ <div class="date-select-wrapper" x-data='daytripDateSelect({
hotelName: "{hotel.name}", hotelName: "<f:format.raw>{hotel.name}</f:format.raw>",
hotelUid: {hotel.uid}, hotelUid: <f:format.raw>{hotel.uid}</f:format.raw>,
productUid: {product.uid}, productUid: <f:format.raw>{product.uid}</f:format.raw>,
contingentEndpointUri: "<f:format.raw>{ep:uri.ajax(action: 'list', controller: 'AjaxContingent', format: 'json', pageUid: settings.defaultAjaxUid)}</f:format.raw>", contingentEndpointUri: "<f:format.raw>{ep:uri.ajax(action: 'list', controller: 'AjaxContingent', format: 'json', pageUid: settings.defaultAjaxUid)}</f:format.raw>",
roomsEndpointUri: "<f:format.raw>{ep:uri.ajax(action: 'rooms', controller: 'AjaxContingent', format: 'json', pageUid: settings.defaultAjaxUid)}</f:format.raw>" roomsEndpointUri: "<f:format.raw>{ep:uri.ajax(action: 'rooms', controller: 'AjaxContingent', format: 'json', pageUid: settings.defaultAjaxUid)}</f:format.raw>"
})'> })'>
@@ -6,10 +6,12 @@
<f:section name="Main"> <f:section name="Main">
<div id="searchbar" x-data='searchBar({ <div id="searchbar" x-data='searchBar({
uri: "<ep:uri.ajax controller="AjaxSearchbar" action="update" pageUid="{settings.defaultAjaxUid}" />", <f:format.raw>
initialSearchParams: {searchParams -> f:format.json() -> f:format.raw()}, uri: "{ep:uri.ajax(controller: "AjaxSearchbar", action: "update", pageUid: settings.defaultAjaxUid)}",
initialFilterOptions: {filterOptions -> f:format.json() -> f:format.raw()}, initialSearchParams: {searchParams -> f:format.json()},
datePresets: {settings.datePickerPresets -> f:format.json() -> f:format.raw()} initialFilterOptions: {filterOptions -> f:format.json()},
datePresets: {settings.datePickerPresets -> f:format.json()}
</f:format.raw>
})'> })'>
<div class="container searchbar{f:if(condition: fixed, then: ' affix')}"{f:if(condition: fixed, else: ' x-data="sticky(650)" x-bind:class="sticky && \'affix\'"')}"> <div class="container searchbar{f:if(condition: fixed, then: ' affix')}"{f:if(condition: fixed, else: ' x-data="sticky(650)" x-bind:class="sticky && \'affix\'"')}">
<div class="container"> <div class="container">
@@ -22,7 +24,6 @@
<f:form.hidden property="dateTo" value="" additionalAttributes="{x-model: 'searchParams.dateTo'}"/> <f:form.hidden property="dateTo" value="" additionalAttributes="{x-model: 'searchParams.dateTo'}"/>
<f:form.hidden property="nights" additionalAttributes="{x-model: 'searchParams.nights'}"/> <f:form.hidden property="nights" additionalAttributes="{x-model: 'searchParams.nights'}"/>
<f:form.hidden property="pax" additionalAttributes="{x-model: 'searchParams.pax'}"/> <f:form.hidden property="pax" additionalAttributes="{x-model: 'searchParams.pax'}"/>
<f:form.hidden property="conceptUid" additionalAttributes="{x-model: 'searchParams.conceptUid'}"/>
<f:form.hidden property="priceRange" additionalAttributes="{x-model: 'searchParams.priceRange'}"/> <f:form.hidden property="priceRange" additionalAttributes="{x-model: 'searchParams.priceRange'}"/>
<f:form.hidden property="pageUid" additionalAttributes="{x-model: 'searchParams.pageUid'}"/> <f:form.hidden property="pageUid" additionalAttributes="{x-model: 'searchParams.pageUid'}"/>
<div class="row"> <div class="row">
@@ -108,7 +109,7 @@
</a> </a>
</div> </div>
<div class="w-1/2"> <div class="w-1/2">
<ul class="plain-list"> <ul class="plain-list mb-0">
<template x-for="region in destination.regions"> <template x-for="region in destination.regions">
<li> <li>
<a href="#" x-on:click.prevent="selectDestination(region.uid, region.label, 'region')" x-text="region.label"></a> <a href="#" x-on:click.prevent="selectDestination(region.uid, region.label, 'region')" x-text="region.label"></a>
@@ -125,10 +126,10 @@
<strong>Konzept</strong> <strong>Konzept</strong>
</div> </div>
<div class="w-1/2"> <div class="w-1/2">
<ul class="plain-list"> <ul class="plain-list mb-0">
<template x-for="concept in filterOptions.concepts"> <template x-for="concept in filterOptions.concepts">
<li> <li>
<a href="#" x-on:click.prevent="selectConcept(concept.uid, concept.name)" x-text="concept.name"></a> <a href="#" x-on:click.prevent="selectDestination(concept.uid, concept.name, 'concept')" x-text="concept.name"></a>
</li> </li>
</template> </template>
</ul> </ul>
@@ -12,56 +12,20 @@
<div class="pagehead__image" <div class="pagehead__image"
style="background-image: url({f:uri.image(src: settings.searchPageHeaderImage, width: '1366', height: '160c+100')})"></div> style="background-image: url({f:uri.image(src: settings.searchPageHeaderImage, width: '1366', height: '160c+100')})"></div>
</div> </div>
<div class="container"> <div class="container relative"
<div class="row"> x-data='search({
<div class="col-md-3 relative" x-data="collapse(false, 991)"> <f:format.raw>
<p class="lg:hidden"> uri: "{ep:uri.ajax(controller: 'AjaxSearch', action: 'searchresult', pageUid: settings.defaultAjaxUid, format: 'html')}",
<button class="button button--full button--small" role="button" x-bind="trigger"> initialFilterSettings: {filterSettings -> f:format.json()},
<i class="fa fa-toggle-on"></i> Filter ein-/ausblenden datePresets: {settings.datePickerPresets -> f:format.json()}
</button> </f:format.raw>
</p> })'>
<f:spaceless> <div class="row" x-ref="container"></div>
<div class="lg:block" <div x-show="loading" class="absolute z-20 inset-0 w-full h-fill bg-overlay-white">
x-bind="container" <div class="absolute mt-16 top-0 left-1/2 -translate-x-1/2 flex items-center space-x-4">
x-transition <f:image src="EXT:ep_theme/Resources/Public/images/ajax-loader-white.gif" alt="Loading" />
x-cloak <span>Suchergebnis wird geladen...</span>
x-data='filterPanel({
<f:format.raw>
uri: "{ep:uri.ajax(controller: 'AjaxFilterpanel', action: 'panel', pageUid: settings.defaultAjaxUid, format: 'html')}",
initialFilterSettings: {filterSettings -> f:format.json()},
datePresets: {settings.datePickerPresets -> f:format.json()}
</f:format.raw>
})'>
<h3>Filter</h3>
<button class="button button--sidebar button--small"
x-show="filterCount > 0"
x-cloak
x-on:click.prevent="reset">
Filter zurücksetzen
</button>
<div x-ref="panel"></div>
<div x-show="loading" class="absolute z-20 inset-0 w-full h-fill bg-overlay-white">
<f:image src="EXT:ep_theme/Resources/Public/images/ajax-loader-white.gif" alt="Loading" class="absolute top-1/2 left-1/2 -translate-xy-1/2"/>
</div>
</div>
</f:spaceless>
</div> </div>
<f:spaceless>
<div class="col-md-9 relative"
x-data='searchResult({
<f:format.raw>
uri: "{ep:uri.ajax(controller: 'AjaxSearch', action: 'searchresult', pageUid: settings.defaultAjaxUid, format: 'html')}",
initialFilterSettings: {filterSettings -> f:format.json()}
</f:format.raw>
})'
x-on:filter-settings-updated.window="onFilterSettingsUpdated($event)"
>
<div x-ref="results"></div>
<div x-show="loading" class="absolute z-20 inset-0 w-full h-fill bg-overlay-white">
<f:image src="EXT:ep_theme/Resources/Public/images/ajax-loader-white.gif" alt="Loading" class="absolute top-1/2 left-1/2 -translate-xy-1/2"/>
</div>
</div>
</f:spaceless>
</div> </div>
</div> </div>
</div> </div>