feat: migrate to new myep app for bookings
This commit is contained in:
@@ -168,7 +168,6 @@ class AjaxTableController extends ActionController
|
||||
'dateTableData' => $dates,
|
||||
'template' => $this->settings['bpnBookingUrlTemplateCode'],
|
||||
'paCode' => $paCode,
|
||||
'isDayTrip' => $product->isDaytrip(),
|
||||
'showBookingButton' => false === $product->getHideBookingButton(),
|
||||
'nonBookableDateUids' => $nonBookableDateUids,
|
||||
]);
|
||||
|
||||
@@ -200,7 +200,7 @@ class ProductRepository extends AbstractRepository
|
||||
'date.uid as dateUid', 'date.date_start as dateStart', 'date.date_end as dateEnd',
|
||||
'date.bus_pro_id as dateBusProId', 'date.hotel as hotelUid', 'date.hotel_bus_pro_id as hotelBusProId',
|
||||
'date.bus_included as dateBusIncluded', 'date.skipass_included as dateSkipassIncluded',
|
||||
'date.season as season',
|
||||
'date.season as season', 'date.product_detail_page as productDetailPage',
|
||||
'room.uid as roomUid', 'room.bus_pro_id as roomBusProId', 'room.name as roomName',
|
||||
'room.price as roomPrice', 'date.discount as roomDiscount', 'date.bus_price as busPrice',
|
||||
'room.services as roomOptionalServices', 'room.nights as roomNights', 'room.available as roomAvailable'
|
||||
@@ -259,7 +259,7 @@ class ProductRepository extends AbstractRepository
|
||||
'date.bus_included as dateBusIncluded', 'date.services_included as dateServicesIncluded',
|
||||
'date.services_general as dateServicesGeneral', 'date.skipass_included as dateSkipassIncluded',
|
||||
'date.bus_pro_id as dateBusProId', 'date.hotel_bus_pro_id as hotelBusProId',
|
||||
'date.available as available'
|
||||
'date.available as available', 'date.product_detail_page as productDetailPage',
|
||||
)
|
||||
->from('tx_epproducts_domain_model_date', 'date')
|
||||
->innerJoin('date', 'tx_epproducts_domain_model_room', 'room', 'room.date = date.uid')
|
||||
|
||||
@@ -89,13 +89,13 @@ class ContingentDataService
|
||||
$data = $this->contingentRepository->getAvailableRooms($dateFrom, $dateTo, $hotel, $product);
|
||||
$period = new Period($dateFrom, $dateTo);
|
||||
$nights = $period->dateInterval()->format('%a');
|
||||
|
||||
$rooms = [];
|
||||
foreach ($data as $row) {
|
||||
$row['bookingUrl'] = BookingUrlUtility::generateUrl([
|
||||
'bookingId' => $row['busProId'],
|
||||
'dateId' => $row['busProId'],
|
||||
'hotelId' => $row['hotelBusProId'],
|
||||
'dateEnd' => $dateTo,
|
||||
'template' => $template,
|
||||
'theme' => $template,
|
||||
]);
|
||||
|
||||
$row['summer'] = $row['season'] === 's';
|
||||
|
||||
@@ -132,7 +132,6 @@ class DateService implements SingletonInterface
|
||||
'priceTableData' => $this->productRepository->getPricetable($product, $hotel, $filterSettings, $date),
|
||||
'template' => $template,
|
||||
'paCode' => $paCode,
|
||||
'isDayTrip' => $product->isDaytrip(),
|
||||
'showBookingButton' => false === $product->getHideBookingButton(),
|
||||
'nonBookableDateUids' => $nonBookableDateUids,
|
||||
]);
|
||||
@@ -194,9 +193,8 @@ class DateService implements SingletonInterface
|
||||
public function preprocessPriceTable(array $options): array
|
||||
{
|
||||
$resolver = new OptionsResolver();
|
||||
$resolver->setDefined(['paCode', 'isDayTrip', 'showBookingButton', 'nonBookableDateUids']);
|
||||
$resolver->setDefined(['paCode', 'showBookingButton', 'nonBookableDateUids']);
|
||||
$resolver->setRequired(['priceTableData', 'template']);
|
||||
$resolver->setDefault('isDayTrip', false);
|
||||
$resolver->setDefault('showBookingButton', true);
|
||||
$resolvedOptions = $resolver->resolve($options);
|
||||
|
||||
@@ -208,7 +206,6 @@ class DateService implements SingletonInterface
|
||||
$priceTableData = $resolvedOptions['priceTableData'];
|
||||
$template = $resolvedOptions['template'];
|
||||
$paCode = $resolvedOptions['paCode'];
|
||||
$isDayTrip = $resolvedOptions['isDayTrip'];
|
||||
$nonBookableDateUids = $resolvedOptions['nonBookableDateUids'];
|
||||
|
||||
foreach ($priceTableData as $row) {
|
||||
@@ -217,12 +214,11 @@ class DateService implements SingletonInterface
|
||||
$dateStart = new \DateTime($row['dateStart']);
|
||||
$dateEnd = new \DateTime($row['dateEnd']);
|
||||
$bookingUrl = BookingUrlUtility::generateUrl([
|
||||
'bookingId' => $row['dateBusProId'],
|
||||
'dateId' => $row['dateBusProId'],
|
||||
'hotelId' => $row['hotelBusProId'],
|
||||
'dateEnd' => $row['dateEnd'],
|
||||
'template' => $template,
|
||||
'theme' => $template,
|
||||
'paCode' => $paCode,
|
||||
'isDayTrip' => $isDayTrip,
|
||||
'returnPageUid' => $row['productDetailPage'] ?? null,
|
||||
]);
|
||||
$optionalServices = $this->preprocessOptionalServices(unserialize($row['roomOptionalServices'], ['allowed_classes' => false]));
|
||||
|
||||
@@ -284,9 +280,8 @@ class DateService implements SingletonInterface
|
||||
public function preprocessDatesTable(array $options): array
|
||||
{
|
||||
$resolver = new OptionsResolver();
|
||||
$resolver->setDefined(['paCode', 'isDayTrip', 'showBookingButton', 'nonBookableDateUids']);
|
||||
$resolver->setDefined(['paCode', 'showBookingButton', 'nonBookableDateUids']);
|
||||
$resolver->setRequired(['dateTableData', 'template']);
|
||||
$resolver->setDefault('isDayTrip', false);
|
||||
$resolver->setDefault('showBookingButton', true);
|
||||
$resolver->setDefault('nonBookableDateUids', []);
|
||||
$resolvedOptions = $resolver->resolve($options);
|
||||
@@ -296,7 +291,6 @@ class DateService implements SingletonInterface
|
||||
$dateTableData = $resolvedOptions['dateTableData'];
|
||||
$template = $resolvedOptions['template'];
|
||||
$paCode = $resolvedOptions['paCode'];
|
||||
$isDayTrip = $resolvedOptions['isDayTrip'];
|
||||
$nonBookableDateUids = $resolvedOptions['nonBookableDateUids'];
|
||||
|
||||
foreach ($dateTableData as $row) {
|
||||
@@ -310,12 +304,11 @@ class DateService implements SingletonInterface
|
||||
$dateStart = new \DateTime($row['dateStart']);
|
||||
$dateEnd = new \DateTime($row['dateEnd']);
|
||||
$bookingUrl = BookingUrlUtility::generateUrl([
|
||||
'bookingId' => $row['dateBusProId'],
|
||||
'dateId' => $row['dateBusProId'],
|
||||
'hotelId' =>$row['hotelBusProId'],
|
||||
'dateEnd' => $row['dateEnd'],
|
||||
'template' => $template,
|
||||
'theme' => $template,
|
||||
'paCode' => $paCode,
|
||||
'isDayTrip' => $isDayTrip,
|
||||
'returnPageUid' => $row['productDetailPage'] ?? null,
|
||||
]);
|
||||
$dateTable[] = [
|
||||
'dateUid' => $row['dateUid'],
|
||||
@@ -386,11 +379,11 @@ class DateService implements SingletonInterface
|
||||
'hotelTitle' => $row['hotelTitle'],
|
||||
'hotelDetailPageUid' => $row['hotelMappingDetailPageUid'] ?? $row['hotelDetailPageUid'],
|
||||
'bookingUrl' => BookingUrlUtility::generateUrl([
|
||||
'bookingId' => $row['dateBusProId'],
|
||||
'dateId' => $row['dateBusProId'],
|
||||
'hotelId' => $row['hotelBusProId'],
|
||||
'dateEnd' => $row['dateEnd'],
|
||||
'template' => $template,
|
||||
'theme' => $template,
|
||||
'paCode' => $paCode,
|
||||
'returnPageUid' => $row['productDetailPage'] ?? null,
|
||||
]),
|
||||
'rooms' => [],
|
||||
];
|
||||
|
||||
@@ -145,10 +145,9 @@ class ResellerDataService implements SingletonInterface
|
||||
$data[$row['dateStart']] = $date;
|
||||
}
|
||||
|
||||
$data[$row['dateStart']]['bookingurl'] = BookingUrlUtility::generateResellerUrl([
|
||||
'bookingId' => $row['dateBusProId'],
|
||||
$data[$row['dateStart']]['bookingurl'] = BookingUrlUtility::generateUrl([
|
||||
'dateId' => $row['dateBusProId'],
|
||||
'hotelId' => $row['hotelBusProId'],
|
||||
'dateEnd' => $row['dateEnd'],
|
||||
'paCode' => $paCode,
|
||||
]);
|
||||
|
||||
|
||||
@@ -2,145 +2,45 @@
|
||||
|
||||
namespace EP\EpProducts\Utility;
|
||||
|
||||
/***************************************************************
|
||||
*
|
||||
* 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 Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
|
||||
use TYPO3\CMS\Core\Routing\RouterInterface;
|
||||
use TYPO3\CMS\Core\Site\SiteFinder;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
class BookingUrlUtility
|
||||
{
|
||||
|
||||
const COOKIE_NAME = 'ep_trk';
|
||||
|
||||
public static function generateUrl(array $options): string
|
||||
{
|
||||
$config = GeneralUtility::makeInstance(ExtensionConfiguration::class)
|
||||
->get('ep_products');
|
||||
|
||||
$resolver = new OptionsResolver();
|
||||
$resolver->setDefined('paCode');
|
||||
$resolver->setRequired(['bookingId', 'hotelId', 'dateEnd']);
|
||||
$resolver->setDefined(['paCode', 'returnPageUid']);
|
||||
$resolver->setRequired(['dateId', 'hotelId']);
|
||||
$resolver->setDefaults([
|
||||
'template' => 'base',
|
||||
'isDayTrip' => false,
|
||||
'theme' => 'base',
|
||||
]);
|
||||
|
||||
$resolvedOptions = $resolver->resolve($options);
|
||||
|
||||
$bookingId = $resolvedOptions['bookingId'];
|
||||
$hotelId = $resolvedOptions['hotelId'];
|
||||
$dateEnd = $resolvedOptions['dateEnd'];
|
||||
$template = $resolvedOptions['template'];
|
||||
$paCode = $resolvedOptions['paCode'];
|
||||
$isDayTrip = $resolvedOptions['isDayTrip'];
|
||||
|
||||
$query = static::buildQuery($bookingId, $hotelId, $dateEnd, $template, $isDayTrip);
|
||||
|
||||
if ('' === session_id()) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
$trackingCode = $_SESSION['ep_trk'] ?? null;
|
||||
|
||||
if (null !== $paCode) {
|
||||
$query['agenturcode'] = $paCode;
|
||||
} elseif (null !== $trackingCode) {
|
||||
$queryParameter = static::getQueryParameter($trackingCode);
|
||||
$query[$queryParameter] = $trackingCode;
|
||||
}
|
||||
|
||||
return static::buildUrl($query);
|
||||
}
|
||||
|
||||
public static function generateResellerUrl(array $options): string
|
||||
{
|
||||
$resolver = new OptionsResolver();
|
||||
$resolver->setDefined('paCode');
|
||||
$resolver->setRequired(['bookingId', 'hotelId', 'dateEnd']);
|
||||
$resolver->setDefaults([
|
||||
'template' => 'base',
|
||||
'isDayTrip' => false,
|
||||
]);
|
||||
|
||||
$resolvedOptions = $resolver->resolve($options);
|
||||
|
||||
$bookingId = $resolvedOptions['bookingId'];
|
||||
$hotelId = $resolvedOptions['hotelId'];
|
||||
$dateEnd = $resolvedOptions['dateEnd'];
|
||||
$template = $resolvedOptions['template'];
|
||||
$paCode = $resolvedOptions['paCode'];
|
||||
$isDayTrip = $resolvedOptions['isDayTrip'];
|
||||
|
||||
$query = static::buildQuery($bookingId, $hotelId, $dateEnd, $template, $isDayTrip);
|
||||
|
||||
if ($paCode !== null) {
|
||||
$query['agenturcode'] = $paCode;
|
||||
}
|
||||
|
||||
return static::buildUrl($query);
|
||||
}
|
||||
|
||||
protected static function buildQuery(string $bookingId, string $hotelId, ?string $dateEnd, string $template = 'base', bool $isDayTrip = false): array
|
||||
{
|
||||
$query = [
|
||||
'id' => $bookingId,
|
||||
'hotelid' => $hotelId,
|
||||
'date_id' => $resolvedOptions['dateId'],
|
||||
'hotel_id' => $resolvedOptions['hotelId'],
|
||||
'theme' => $resolvedOptions['theme'],
|
||||
];
|
||||
|
||||
if ($dateEnd && !$isDayTrip) {
|
||||
$query['ruecktermin'] = date('d.m.Y', strtotime($dateEnd));
|
||||
if (null !== $resolvedOptions['paCode']) {
|
||||
$query['agency'] = $resolvedOptions['paCode'];
|
||||
}
|
||||
|
||||
if ($template) {
|
||||
$query['templatezusatz'] = '_' . $template;
|
||||
if (null !== $resolvedOptions['returnPageUid'] && 0 < (int) $resolvedOptions['returnPageUid']) {
|
||||
$siteFinder = GeneralUtility::makeInstance(SiteFinder::class);
|
||||
$site = $siteFinder->getSiteByPageId((int) $resolvedOptions['returnPageUid']);
|
||||
|
||||
$query['r'] = (string) $site->getRouter()->generateUri((int) $resolvedOptions['returnPageUid'], [], '', RouterInterface::ABSOLUTE_URL);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
protected static function buildUrl(array $query): string
|
||||
{
|
||||
/** @var SiteFinder $siteFinder */
|
||||
$siteFinder = GeneralUtility::makeInstance(SiteFinder::class);
|
||||
|
||||
$pageId = $GLOBALS['TSFE']->id;
|
||||
|
||||
if (null !== $pageId) {
|
||||
$site = $siteFinder->getSiteByPageId($pageId);
|
||||
} else {
|
||||
$site = $siteFinder->getSiteByIdentifier('ep-reisen');
|
||||
}
|
||||
|
||||
return $site->getBase() . '/buchung/maske_buchung.php?' . http_build_query($query);
|
||||
}
|
||||
|
||||
protected static function getQueryParameter($code)
|
||||
{
|
||||
if (!preg_match('/^PA\d{3,4}$/', $code)) {
|
||||
return 'crminfo';
|
||||
}
|
||||
|
||||
return 'agenturcode';
|
||||
return $config['myEpUrl'].'/bookings/create?'.http_build_query($query);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
# cat=General; type=int; label=Searchresult PID
|
||||
searchPageUid = 0
|
||||
|
||||
# cat=MyEp; type=string; label=MyE&P URL
|
||||
myEpUrl =
|
||||
|
||||
# cat=MyEpAPI; type=string; label=MyE&P API base URL (trailing slash!)
|
||||
myEpApiBaseUrl =
|
||||
|
||||
|
||||
@@ -35,6 +35,10 @@ $boot = function () {
|
||||
],
|
||||
];
|
||||
|
||||
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTypoScriptConstants(
|
||||
'plugin.tx_epproducts.settings.myEpUrl = '.$GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['ep_products']['myEpUrl']
|
||||
);
|
||||
|
||||
$iconRegistry = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\Imaging\IconRegistry::class);
|
||||
$iconRegistry->registerIcon(
|
||||
'ep-logo',
|
||||
|
||||
@@ -82,9 +82,9 @@ class BookingViewHelper extends AbstractViewHelper
|
||||
}
|
||||
|
||||
$options = [
|
||||
'bookingId' => $arguments['bookingId'],
|
||||
'dateId' => $arguments['bookingId'],
|
||||
'hotelId' => $arguments['hotelId'],
|
||||
'template' => $template,
|
||||
'theme' => $template,
|
||||
];
|
||||
return BookingUrlUtility::generateUrl($options);
|
||||
}
|
||||
|
||||
@@ -76,6 +76,10 @@ plugin.tx_eptheme {
|
||||
globalConceptCode = {$plugin.tx_eptheme.settings.globalConceptCode}
|
||||
travelAlertPageUid = {$plugin.tx_eptheme.settings.travelAlertPageUid}
|
||||
myEpPageUid = {$plugin.tx_eptheme.settings.myEpPageUid}
|
||||
myEpApiEndpointUrl = {$plugin.tx_eptheme.settings.myEpApiEndpointUrl}
|
||||
myEpApiToken = {$plugin.tx_eptheme.settings.myEpApiToken}
|
||||
myEpApiTimeout = {$plugin.tx_eptheme.settings.myEpApiTimeout}
|
||||
myEpUrl = {$plugin.tx_epproducts.settings.myEpUrl}
|
||||
facebookViewContentValues {
|
||||
productUid = {$plugin.tx_eptheme.settings.facebookViewContentProductUid}
|
||||
nameInternal = {$plugin.tx_eptheme.settings.facebookViewContentNameInternal}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
// Initialize vue app
|
||||
import Vue from 'vue'
|
||||
import vueCustomElement from 'vue-custom-element'
|
||||
import dayjs from 'dayjs'
|
||||
import 'dayjs/locale/de'
|
||||
import 'document-register-element/build/document-register-element'
|
||||
import App from './myep/App'
|
||||
|
||||
Vue.filter('date', (value) => {
|
||||
return dayjs(value).format('DD.MM.YY');
|
||||
})
|
||||
|
||||
Vue.use(vueCustomElement)
|
||||
Vue.config.productionTip = false
|
||||
|
||||
Vue.customElement('my-ep', App)
|
||||
@@ -1,130 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<p class="text-2xl">Adressdaten</p>
|
||||
<form @submit.prevent="save()">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
<div>
|
||||
<form-group label="Gender">
|
||||
<select v-model="userData.gender" class="border-zinc-400 focus:border-zinc-800 ring-0 focus:ring-0 mt-1 w-full">
|
||||
<option value="w">W</option>
|
||||
<option value="m">M</option>
|
||||
<option value="d">D</option>
|
||||
</select>
|
||||
</form-group>
|
||||
<form-group label="Title">
|
||||
<input type="text" v-model="userData.title" class="border-zinc-400 focus:border-zinc-800 ring-0 focus:ring-0 mt-1 w-full">
|
||||
</form-group>
|
||||
<form-group label="Name" :error="formErrors.lastName">
|
||||
<input type="text" v-model="userData.lastName" class="border-zinc-400 focus:border-zinc-800 ring-0 focus:ring-0 mt-1 w-full">
|
||||
</form-group>
|
||||
<form-group label="Vorname" :error="formErrors.firstName">
|
||||
<input type="text" v-model="userData.firstName" class="border-zinc-400 focus:border-zinc-800 ring-0 focus:ring-0 mt-1 w-full">
|
||||
</form-group>
|
||||
<form-group label="Geburtsdatum">
|
||||
<div ref="picker"></div>
|
||||
</form-group>
|
||||
</div>
|
||||
<div>
|
||||
<form-group label="Straße" :error="formErrors.street">
|
||||
<input type="text" v-model="userData.street" class="border-zinc-400 focus:border-zinc-800 ring-0 focus:ring-0 mt-1 w-full">
|
||||
</form-group>
|
||||
<form-group label="PLZ" :error="formErrors.zipCode">
|
||||
<input type="text" v-model="userData.zipCode" class="border-zinc-400 focus:border-zinc-800 ring-0 focus:ring-0 mt-1 w-full">
|
||||
</form-group>
|
||||
<form-group label="Stadt" :error="formErrors.city">
|
||||
<input type="text" v-model="userData.city" class="border-zinc-400 focus:border-zinc-800 ring-0 focus:ring-0 mt-1 w-full">
|
||||
</form-group>
|
||||
<form-group label="Land" :error="formErrors.country">
|
||||
<select v-model="userData.country" class="border-zinc-400 focus:border-zinc-800 ring-0 focus:ring-0 mt-1 w-full">
|
||||
<option v-for="country in countries"
|
||||
:value="country['code']">{{ country['name'] }}</option>
|
||||
</select>
|
||||
</form-group>
|
||||
<form-group label="E-Mail" :error="formErrors.email">
|
||||
<input type="text" v-model="userData.email" class="border-zinc-400 focus:border-zinc-800 ring-0 focus:ring-0 mt-1 w-full">
|
||||
</form-group>
|
||||
<form-group label="Telefon" :error="formErrors.phonePrivate">
|
||||
<input type="text" v-model="userData.phonePrivate" class="border-zinc-400 focus:border-zinc-800 ring-0 focus:ring-0 mt-1 w-full">
|
||||
</form-group>
|
||||
<form-group label="Mobil" :error="formErrors.phoneMobile">
|
||||
<input type="text" v-model="userData.phoneMobile" class="border-zinc-400 focus:border-zinc-800 ring-0 focus:ring-0 mt-1 w-full">
|
||||
</form-group>
|
||||
</div>
|
||||
<div class="col-span-1 md:col-span2">
|
||||
<button type="submit" class="button bg-button" :disabled="loading">
|
||||
Speichern
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Vue from 'vue'
|
||||
import { mapState, mapMutations, mapActions } from 'vuex'
|
||||
import flatpickr from 'flatpickr'
|
||||
import { German } from 'flatpickr/dist/l10n/de'
|
||||
import FormGroup from './components/FormGroup'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FormGroup
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
picker: null,
|
||||
formErrors: {},
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.loadAddress()
|
||||
.then(() => {
|
||||
this.picker.setDate(new Date(this.userData.dateOfBirth), true);
|
||||
this.$emit('loaded')
|
||||
})
|
||||
.catch(() => {
|
||||
this.logout()
|
||||
this.$router.push({name: 'login'})
|
||||
})
|
||||
},
|
||||
mounted () {
|
||||
Vue.nextTick(() => {
|
||||
this.picker = flatpickr(this.$refs.picker, {
|
||||
dateFormat: 'd.m.Y',
|
||||
locale: German,
|
||||
inline: true,
|
||||
onChange: selectedDates => {
|
||||
this.userData.dateOfBirth = flatpickr.formatDate(selectedDates[0], 'Y-m-d')
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(['logout', 'alert']),
|
||||
...mapActions(['loadAddress', 'saveAddress']),
|
||||
save () {
|
||||
this.formErrors = {}
|
||||
this.saveAddress(this.userData)
|
||||
.catch(error => {
|
||||
this.alert({
|
||||
message: 'Bitte überprüfe deine Eingaben.',
|
||||
success: false,
|
||||
})
|
||||
const violations = error.violations
|
||||
if (typeof violations !== 'undefined') {
|
||||
for (let violation of violations) {
|
||||
this.$set(this.formErrors, violation.property_path, violation.message)
|
||||
}
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.$emit('loaded')
|
||||
})
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['loading', 'userData', 'countries'])
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,82 +0,0 @@
|
||||
<template>
|
||||
<div class="relative mb-16">
|
||||
<a ref="container" class="block w-0 h-0 pt-24 -mt-24"></a>
|
||||
<nav class="w-full bg-zinc-50 flex items-center mb-8 relative" v-if="isLoggedIn">
|
||||
<div class="flex-1 block text-2xl px-4">My E&P</div>
|
||||
<div class="bg-zinc-50 shadow w-full lg:w-auto absolute lg:relative z-10 lg:z-auto top-full lg:top-auto left-0 lg:shadow-none lg:flex lg:space-x-4"
|
||||
:class="{ 'hidden': !navVisible }">
|
||||
<div class="lg:flex lg:items-center m-0">
|
||||
<router-link @click.native="navVisible = false" :to="{ name: 'address' }" class="block p-4 text-black hover:bg-zinc-400 hover:text-zinc-800" active-class="bg-zinc-400 text-zinc-800">Adressdaten</router-link>
|
||||
<router-link @click.native="navVisible = false" :to="{ name: 'newsletter' }" class="block p-4 text-black hover:bg-zinc-400 hover:text-zinc-800" active-class="bg-zinc-400 text-zinc-800">Newsletter</router-link>
|
||||
<router-link @click.native="navVisible = false" :to="{ name: 'profile' }" class="block p-4 text-black hover:bg-zinc-400 hover:text-zinc-800" active-class="bg-zinc-400 text-zinc-800">Mein Profil</router-link>
|
||||
<router-link @click.native="navVisible = false" :to="{ name: 'events' }" class="block p-4 text-black hover:bg-zinc-400 hover:text-zinc-800" active-class="bg-zinc-400 text-zinc-800">Buchungen</router-link>
|
||||
</div>
|
||||
<a class="block px-4 py-4 text-black hover:bg-zinc-400 hover:text-zinc-800" href="#" @click.prevent="performLogout()">Logout</a>
|
||||
</div>
|
||||
<a href="#" class="lg:hidden p-4 text-black hover:text-zinc-800" :class="{ 'rotate-90': navVisible }" @click.prevent="navVisible = !navVisible">
|
||||
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path></svg>
|
||||
</a>
|
||||
</nav>
|
||||
<div class="p-4 mb-8 text-white" :class="{'bg-emerald-600': alert.success, 'bg-red-600': !alert.success}" v-if="alert.message" v-html="alert.message"></div>
|
||||
<div class="relative">
|
||||
<router-view @loaded="scrollTop()"></router-view>
|
||||
<div class="absolute top-0 left-0 inset-0 bg-white/85" v-show="loading">
|
||||
<div class="absolute top-1/2 left-1/2 transform -translate-xy-1/2 flex items-center space-x-2 h-8">
|
||||
<img :src="config.loaderUrl" alt="" class="block w-4 h-4">
|
||||
<span v-text="statusMessage"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapState, mapActions, mapMutations } from 'vuex'
|
||||
import store from './store'
|
||||
import router from './router'
|
||||
|
||||
export default {
|
||||
store,
|
||||
router,
|
||||
methods: {
|
||||
performLogout () {
|
||||
this.logout()
|
||||
.then(() => {
|
||||
this.$router.replace({ name: 'login' })
|
||||
}).catch(error => {});
|
||||
},
|
||||
scrollTop() {
|
||||
this.$refs.container.scrollIntoView({behavior: 'smooth', block: 'start'})
|
||||
},
|
||||
...mapMutations([
|
||||
'initConfig',
|
||||
]),
|
||||
...mapActions([
|
||||
'checkLoginState',
|
||||
'loadCountries',
|
||||
'logout',
|
||||
]),
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
navVisible: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['config', 'loading', 'alert', 'statusMessage']),
|
||||
...mapGetters(['isLoggedIn', 'isTeamer'])
|
||||
},
|
||||
mounted () {
|
||||
this.initConfig()
|
||||
this.checkLoginState()
|
||||
.then(() => {
|
||||
if (this.isLoggedIn) {
|
||||
this.loadCountries()
|
||||
.then(() => {
|
||||
this.$router.replace({ name: 'address' })
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,548 +0,0 @@
|
||||
<template>
|
||||
<div class="wrapper">
|
||||
|
||||
<div class="grid grid-cols-2 gap-x-2 pb-4">
|
||||
<button type="button" class="button bg-button" :class="{ 'bg-button--secondary': tab === 'data' }" @click="tab = 'data'">
|
||||
Buchungsübersicht
|
||||
</button>
|
||||
<button type="button" class="button bg-button" :class="{ 'bg-button--secondary': tab === 'participants' }" @click="tab = 'participants'">
|
||||
Buchung ändern
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="initialized" v-show="tab === 'data'">
|
||||
<h2 class="mb-4">
|
||||
Reisedaten
|
||||
</h2>
|
||||
<div class="grid lg:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<panel title="Allgemein">
|
||||
<ul class="list-none">
|
||||
<li class="p-2 border-b border-zinc-200 last:border-0">
|
||||
<strong>Reise:</strong> {{ travelData.productName }}
|
||||
</li>
|
||||
<li class="p-2 border-b border-zinc-200 last:border-0">
|
||||
<strong>Zeitraum:</strong> {{ travelData.dateFrom }} - {{ travelData.dateTo }}
|
||||
</li>
|
||||
<li class="p-2 border-b border-zinc-200 last:border-0">
|
||||
<strong>Inklusivleistungen:</strong> {{ travelData.servicesIncluded.join(', ') }}
|
||||
</li>
|
||||
<li class="p-2 border-b border-zinc-200 last:border-0">
|
||||
<strong>Anmelder:</strong> {{ bookingData.applicant.firstName }} {{ bookingData.applicant.lastName }}
|
||||
</li>
|
||||
<li class="p-2 border-b border-zinc-200 last:border-0">
|
||||
<strong>Teilnehmer:</strong> {{ participantsCount.active }}
|
||||
</li>
|
||||
<li class="p-2 border-b border-zinc-200 last:border-0">
|
||||
<strong>Stornos:</strong> {{ participantsCount.canceled }}
|
||||
</li>
|
||||
<li class="p-2 border-b border-zinc-200 last:border-0">
|
||||
<strong>Gesamtpreis:</strong> Es gelten die in der Rechnung angegebenen Preise
|
||||
</li>
|
||||
</ul>
|
||||
</panel>
|
||||
<services-overview :services="services.other" :participant-data="participantData"/>
|
||||
</div>
|
||||
<div>
|
||||
<mutable-fields :mutable-fields="mutableFields"></mutable-fields>
|
||||
<contingents-overview :contingents="bookingData.accomodations"/>
|
||||
<panel title="Beförderungsart (alle Teilnehmer)" :body="true">
|
||||
<ul class="list-none">
|
||||
<li class="p-2 border-b border-zinc-200 last:border-0" v-for="transportation in bookingData.transportations" :key="transportation.id">
|
||||
{{ transportation.name }}: {{ transportation.count }}
|
||||
</li>
|
||||
</ul>
|
||||
</panel>
|
||||
</div>
|
||||
</div>
|
||||
<div class="py-4 lg:flex lg:justify-between space-y-1 lg:space-y-0" v-show="!loading" v-if="initialized">
|
||||
<button class="button bg-button" :disabled="!dirty" @click.prevent="storeBookingData()">
|
||||
Buchung aktualisieren
|
||||
</button>
|
||||
<router-link :to="{ name: 'events' }" class="button bg-button">
|
||||
zur Übersicht
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
<div id="participants" class="py-4" v-if="initialized" v-show="tab === 'participants'">
|
||||
<h2 class="mb-4">
|
||||
Teilnehmer
|
||||
</h2>
|
||||
<div class="py-4 lg:flex lg:justify-between space-y-1 lg:space-y-0" v-show="!loading" v-if="initialized && participantData.length > 5">
|
||||
<button class="button bg-button" :disabled="!dirty" @click.prevent="storeBookingData()">
|
||||
Buchung aktualisieren
|
||||
</button>
|
||||
<router-link :to="{ name: 'events' }" class="button bg-button">
|
||||
zur Übersicht
|
||||
</router-link>
|
||||
</div>
|
||||
<collapsible-panel :title="'#' + (index + 1) + ' ' + participant.data.lastName + ', ' + participant.data.firstName"
|
||||
:class="invalidParticipantData[participant.data.id] ? 'panel-danger' : 'panel-default'" parent="#participant-list"
|
||||
v-for="(participant, index) in participantData" :key="participant.data.id">
|
||||
<template v-if="participant.data.canceled">
|
||||
Storniert am {{ participant.data.cancellationDate }}, Stornokosten {{ participant.data.cancellationFee.toFixed(2) }} €
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="p-2 border-b border-zinc-200">
|
||||
<participant-data-form :participant="participant"
|
||||
:mutable="mutableFields.participantData.mutable"
|
||||
:countries="countries"
|
||||
v-on:updated="onParticipantDataUpdated($event, index)"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col space-y-1 md:flex-row md:space-y-0 md:space-x-1 py-4 border-b border-zinc-200 p-2">
|
||||
<participant-address-form :participant="participant"
|
||||
:countries="countries"
|
||||
:mutable="mutableFields.participantData.mutable"
|
||||
v-on:updated="onParticipantDataUpdated($event, index)"
|
||||
/>
|
||||
<accommodation-select :participant="participant"
|
||||
:contingents="availableContingents"
|
||||
:mutable="mutableFields.accommodation.mutable && isGroupManager"
|
||||
v-on:selected="onAccommodationSelected($event, index)"
|
||||
/>
|
||||
<service-select v-for="subType in servicesSubTypes"
|
||||
:key="subType.label"
|
||||
:sub-type="subType"
|
||||
:services="services.other"
|
||||
:participant="participant"
|
||||
:mutable="mutableFields.services.mutable"
|
||||
v-on:updated="onServiceUpdated($event, index)"
|
||||
/>
|
||||
<participant-sizes-form :services="services.other"
|
||||
:participant="participant"
|
||||
:mutable="mutableFields.services.mutable"
|
||||
v-on:updated="onParticipantDataUpdated($event, index)"/>
|
||||
<transportation-select :services="services.transportation"
|
||||
:participant="participant"
|
||||
:mutable="mutableFields.transportation.mutable"
|
||||
v-on:updated="onTransportationUpdated($event)"
|
||||
/>
|
||||
<pickup-select
|
||||
:pickups="services.pickups"
|
||||
:participant="participant"
|
||||
:mutable="mutableFields.transportation.mutable"
|
||||
v-on:selected="onPickupSelected($event, index)"
|
||||
v-if="services.pickups"
|
||||
/>
|
||||
</div>
|
||||
<div class="grid lg:grid-cols-2 gap-4 p-2">
|
||||
<table class="text-sm mb-0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th class="w-1/5">Zimmer</th>
|
||||
<td>{{ getAccommodationLabel(participant) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Zusatzleistungen</th>
|
||||
<td>{{ getSelectedServicesLabels(participant.serviceIds, services.other) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Beförderung</th>
|
||||
<td>{{ getSelectedServicesLabels(participant.transportationIds, services.transportation) }}</td>
|
||||
</tr>
|
||||
<tr v-if="participant.surcharges.length > 0">
|
||||
<th>Zuschläge</th>
|
||||
<td>
|
||||
<ul class="list-none p-0 mb-0">
|
||||
<li v-for="surcharge in participant.surcharges">
|
||||
{{ surcharge.priceSingle.toFixed(2) }} € <span v-show="surcharge.name">({{ surcharge.name }})</span>
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
</collapsible-panel>
|
||||
<div class="py-4 lg:flex lg:justify-between space-y-1 lg:space-y-0" v-show="!loading" v-if="initialized">
|
||||
<button class="button bg-button" :disabled="!dirty" @click.prevent="storeBookingData()">
|
||||
Buchung aktualisieren
|
||||
</button>
|
||||
<router-link :to="{ name: 'events' }" class="button bg-button">
|
||||
zur Übersicht
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AccommodationSelect from './components/AccommodationSelect'
|
||||
import ContingentsOverview from './components/ContingentsOverview'
|
||||
import MutableFields from './components/MutableFields'
|
||||
import Panel from './components/Panel'
|
||||
import CollapsiblePanel from './components/CollapsiblePanel'
|
||||
import ParticipantAddressForm from './components/ParticipantAddressForm'
|
||||
import ParticipantDataForm from './components/ParticipantDataForm'
|
||||
import ParticipantSizesForm from './components/ParticipantSizesForm'
|
||||
import PickupSelect from './components/PickupSelect'
|
||||
import ServiceSelect from './components/ServiceSelect'
|
||||
import ServicesOverview from './components/ServicesOverview'
|
||||
import TransportationSelect from './components/TransportationSelect'
|
||||
import { mapGetters, mapMutations, mapState } from 'vuex'
|
||||
import {checkStatus, defaultOptions} from './api'
|
||||
|
||||
export default {
|
||||
name: 'BookingEdit',
|
||||
components: {
|
||||
Panel,
|
||||
CollapsiblePanel,
|
||||
MutableFields,
|
||||
TransportationSelect,
|
||||
ServicesOverview,
|
||||
ContingentsOverview,
|
||||
AccommodationSelect,
|
||||
ParticipantDataForm,
|
||||
ParticipantAddressForm,
|
||||
ParticipantSizesForm,
|
||||
PickupSelect,
|
||||
ServiceSelect
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
tab: 'data',
|
||||
bookingId: null,
|
||||
initialized: false,
|
||||
dirty: false,
|
||||
processing: false,
|
||||
bookingData: null,
|
||||
participantData: null,
|
||||
invalidParticipantData: {},
|
||||
eventData: null,
|
||||
travelData: null,
|
||||
mutableFields: null,
|
||||
contingents: null,
|
||||
services: null,
|
||||
servicesSubTypes: [
|
||||
{ 'codes': ['KUR'], 'label': 'Kurse' },
|
||||
{ 'codes': ['SON'], 'label': 'Zusatzleistungen' },
|
||||
{ 'codes': ['SPA'], 'label': 'Skipass' },
|
||||
{ 'codes': ['VPF'], 'label': 'Verpflegung' },
|
||||
{ 'codes': ['VER', 'VE2', 'VE3', 'VE4', 'VE5', 'VE6', 'VE7', 'VE8'], 'label': 'Verleih' },
|
||||
{ 'codes': ['EIN'], 'label': '???' }
|
||||
],
|
||||
modal: {
|
||||
title: 'Hinweis',
|
||||
message: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(['alert', 'beginLoading', 'endLoading']),
|
||||
loadBookingData () {
|
||||
this.beginLoading('Lade Reise- und Buchungsdaten...')
|
||||
const options = {
|
||||
...defaultOptions
|
||||
}
|
||||
return fetch(`${this.config.myEpApiBaseUrl}/api/booking/${this.bookingId}`, options)
|
||||
.then(checkStatus)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
this.bookingData = data.bookingData
|
||||
this.participantData = data.participantData
|
||||
this.travelData = data.travelData
|
||||
})
|
||||
.finally(() => {
|
||||
this.endLoading()
|
||||
})
|
||||
},
|
||||
loadEventData () {
|
||||
this.beginLoading('Lade Vorgangsdaten...')
|
||||
const options = {
|
||||
...defaultOptions
|
||||
}
|
||||
return fetch(`${this.config.myEpApiBaseUrl}/api/event/${this.bookingData.bookingNumber}`, options)
|
||||
.then(checkStatus)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
this.eventData = data
|
||||
})
|
||||
.catch(error => {
|
||||
throw new Error(error.message)
|
||||
})
|
||||
.finally(() => {
|
||||
this.endLoading()
|
||||
})
|
||||
},
|
||||
loadMutableFields () {
|
||||
this.beginLoading('Lade mögliche Änderungen...')
|
||||
const options = {
|
||||
...defaultOptions
|
||||
}
|
||||
return fetch(`${this.config.myEpApiBaseUrl}/api/mutable-fields/${this.bookingData.travelId}`, options)
|
||||
.then(checkStatus)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
this.mutableFields = data
|
||||
})
|
||||
.catch(() => {
|
||||
throw new Error('Mögliche Änderungen konnten nicht geladen werden.');
|
||||
})
|
||||
.finally(() => {
|
||||
this.endLoading()
|
||||
})
|
||||
},
|
||||
loadContingents () {
|
||||
this.beginLoading('Lade Kontingente...')
|
||||
const options = {
|
||||
...defaultOptions
|
||||
}
|
||||
return fetch(`${this.config.myEpApiBaseUrl}/api/contingents/${this.bookingData.travelId}/${this.bookingData.partnerId}`, options)
|
||||
.then(checkStatus)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
this.contingents = data
|
||||
})
|
||||
.catch(() => {
|
||||
throw new Error('Kontingente konnten nicht geladen werden.');
|
||||
})
|
||||
.finally(() => {
|
||||
this.endLoading()
|
||||
})
|
||||
},
|
||||
loadServices () {
|
||||
this.beginLoading('Lade Leistungen...')
|
||||
const options = {
|
||||
...defaultOptions
|
||||
}
|
||||
return fetch(`${this.config.myEpApiBaseUrl}/api/services/${this.bookingData.travelId}`, options)
|
||||
.then(checkStatus)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
this.services = data
|
||||
})
|
||||
.catch(() => {
|
||||
throw new Error('Leistungen konnten nicht geladen werden.');
|
||||
})
|
||||
.finally(() => {
|
||||
this.endLoading()
|
||||
})
|
||||
},
|
||||
getAccommodationLabel (participant) {
|
||||
if (participant.hasOwnProperty('accommodationId') && participant.accommodationId in this.contingents) {
|
||||
return this.contingents[participant.accommodationId].name
|
||||
}
|
||||
|
||||
return '-';
|
||||
},
|
||||
getSelectedServicesLabels (serviceIds, services) {
|
||||
const labels = [];
|
||||
for (let serviceId of serviceIds) {
|
||||
if (services[serviceId]) {
|
||||
labels.push(services[serviceId].name);
|
||||
}
|
||||
}
|
||||
return labels.join(', ');
|
||||
},
|
||||
calculateSinglePrice (participant) {
|
||||
let singlePrice = 0;
|
||||
if (participant.hasOwnProperty('accommodationId') && participant.accommodationId in this.contingents) {
|
||||
let accommodation = this.contingents[participant.accommodationId];
|
||||
singlePrice += accommodation.price;
|
||||
}
|
||||
for (let serviceId of participant.serviceIds) {
|
||||
if (this.services.other[serviceId]) {
|
||||
singlePrice += this.services.other[serviceId].price;
|
||||
}
|
||||
}
|
||||
for (let serviceId of participant.transportationIds) {
|
||||
if (this.services.transportation[serviceId]) {
|
||||
singlePrice += this.services.transportation[serviceId].price;
|
||||
}
|
||||
}
|
||||
if ('S' === participant.data.status && participant.data.cancellationFee) {
|
||||
singlePrice += participant.data.cancellationFee;
|
||||
}
|
||||
for (let surcharge of participant.surcharges) {
|
||||
singlePrice += surcharge.priceSingle
|
||||
}
|
||||
return singlePrice;
|
||||
},
|
||||
onAccommodationSelected (accommodation, index) {
|
||||
const participant = this.participantData[index];
|
||||
if (participant.hasOwnProperty('accommodationId')) {
|
||||
const currentAccommodationId = this.participantData[index].accommodationId;
|
||||
if (currentAccommodationId in this.contingents) {
|
||||
this.contingents[currentAccommodationId].available++;
|
||||
}
|
||||
}
|
||||
const newAccommodationId = accommodation.id;
|
||||
if (this.participantData[index].accommodationId === newAccommodationId) {
|
||||
return;
|
||||
}
|
||||
this.participantData[index].accommodationId = newAccommodationId;
|
||||
this.contingents[newAccommodationId].available--;
|
||||
this.dirty = true;
|
||||
},
|
||||
onPickupSelected (pickupId, index) {
|
||||
this.participantData[index].pickupId = pickupId;
|
||||
this.dirty = true;
|
||||
},
|
||||
onParticipantDataUpdated (participant, index) {
|
||||
this.participantData[index] = participant;
|
||||
this.dirty = true;
|
||||
},
|
||||
onServiceUpdated (event, index) {
|
||||
let delta = event.action === 'select' ? -1 : 1;
|
||||
this.services.other[event.service.id].available += delta;
|
||||
this.participantData[index] = event.participant;
|
||||
this.dirty = true;
|
||||
},
|
||||
onTransportationUpdated (event) {
|
||||
let services = this.services.transportation
|
||||
if (event.action === 'select') {
|
||||
services[event.service.id].available--;
|
||||
} else {
|
||||
services[event.service.id].available++;
|
||||
}
|
||||
this.services.transportation = services
|
||||
|
||||
this.dirty = true;
|
||||
},
|
||||
hasSelectedRentals (participant) {
|
||||
let result = false;
|
||||
for (let id of participant.serviceIds) {
|
||||
if (typeof this.services.other[id] === 'undefined') {
|
||||
continue;
|
||||
}
|
||||
if ('VER' === this.services.other[id].subType) {
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
return result
|
||||
},
|
||||
validateInput () {
|
||||
let messages = [];
|
||||
this.invalidParticipantData = {};
|
||||
const emailPattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
|
||||
for (let participant of this.participantData) {
|
||||
let participantId = participant.data.id;
|
||||
if (!emailPattern.test(participant.data.email)) {
|
||||
this.invalidParticipantData[participantId] = {
|
||||
invalid: 'E-Mail',
|
||||
};
|
||||
messages.push('Teilnehmer #'+participantId+': ungültige E-Mail-Adresse')
|
||||
}
|
||||
// if (!participant.data.nationality) {
|
||||
// this.invalidParticipantData[participantId] = {
|
||||
// invalid: 'Nationalität',
|
||||
// };
|
||||
// messages.push('Teilnehmer #'+participantId+': Nationalität fehlt')
|
||||
// }
|
||||
if (!this.hasSelectedRentals(participant)) {
|
||||
continue;
|
||||
}
|
||||
if (!participant.data.height) {
|
||||
this.invalidParticipantData[participantId] = {
|
||||
invalid: 'Körpergröße',
|
||||
};
|
||||
messages.push('Teilnehmer #'+participantId+': Körpergröße fehlt')
|
||||
}
|
||||
if (!participant.data.shoeSize) {
|
||||
this.invalidParticipantData[participantId] = {
|
||||
invalid: 'Schuhgröße',
|
||||
};
|
||||
messages.push('Teilnehmer #'+participantId+': Schuhgröße fehlt')
|
||||
}
|
||||
}
|
||||
return messages
|
||||
},
|
||||
storeBookingData() {
|
||||
let messages = this.validateInput();
|
||||
if (false === this.dataValid) {
|
||||
this.alert({
|
||||
message: messages.join('<br>'),
|
||||
success: false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.beginLoading()
|
||||
const options = {
|
||||
...defaultOptions,
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({
|
||||
participantData: this.participantData,
|
||||
remarks: this.bookingData.remarks,
|
||||
})
|
||||
}
|
||||
fetch(`${this.config.myEpApiBaseUrl}/api/booking/${this.bookingId}`, options)
|
||||
.then(checkStatus)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
this.alert({
|
||||
message: data.message,
|
||||
success: true,
|
||||
})
|
||||
this.dirty = false
|
||||
})
|
||||
.catch(error => {
|
||||
this.alert({
|
||||
message: error.message,
|
||||
success: false,
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
this.endLoading()
|
||||
this.$emit('loaded')
|
||||
})
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['config', 'loading', 'countries']),
|
||||
...mapGetters(['isGroupManager']),
|
||||
availableContingents () {
|
||||
let filteredContingents = {};
|
||||
for (let id of Object.keys(this.contingents)) {
|
||||
if (this.contingents[id].available > 0) {
|
||||
filteredContingents[id] = this.contingents[id];
|
||||
}
|
||||
}
|
||||
return filteredContingents;
|
||||
},
|
||||
totalPrice () {
|
||||
let totalPrice = 0;
|
||||
this.participantData.forEach(participant => {
|
||||
totalPrice += this.calculateSinglePrice(participant);
|
||||
});
|
||||
return totalPrice;
|
||||
},
|
||||
participantsCount () {
|
||||
let active = 0;
|
||||
let canceled = 0;
|
||||
for (const participant of this.participantData) {
|
||||
if (participant.data.canceled) {
|
||||
canceled++;
|
||||
} else {
|
||||
active++;
|
||||
}
|
||||
}
|
||||
return {active: active, canceled: canceled};
|
||||
},
|
||||
dataValid () {
|
||||
return Object.keys(this.invalidParticipantData).length === 0;
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.bookingId = this.$route.params.id
|
||||
this.loadBookingData()
|
||||
.then(() => this.loadEventData())
|
||||
.then(() => this.loadMutableFields())
|
||||
.then(() => this.loadContingents())
|
||||
.then(() => this.loadServices())
|
||||
.then(() => {
|
||||
this.initialized = true;
|
||||
})
|
||||
.catch(error => {
|
||||
this.alert({
|
||||
message: 'Es ist ein Fehler aufgetreten: ' + error.message,
|
||||
success: false,
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
this.$emit('loaded')
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,138 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1 class="mb-4">
|
||||
Mein Profil
|
||||
</h1>
|
||||
<form @submit.prevent="save()">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-2 mb-4">
|
||||
<div>
|
||||
<template v-for="selectionGroup in crmData.selectionGroups"
|
||||
v-if="hasVisibleSelections(selectionGroup)">
|
||||
<h3 class="mb-2">
|
||||
{{ selectionGroup.name}}
|
||||
</h3>
|
||||
<div class="mb-2"
|
||||
v-for="selection in selectionGroup.selections"
|
||||
v-if="selection.changeable === true && isSelectionVisible(selection)">
|
||||
<label class="inline-flex items-center" :class="{ 'opacity-50': !selection.changeable}">
|
||||
<input type="checkbox"
|
||||
class="border-zinc-400 focus:border-zinc-800 ring-0 focus:ring-0 text-ep-secondary sbw:text-sbw-secondary"
|
||||
v-model="selection.value"
|
||||
:disabled="!selection.changeable"/>
|
||||
<span class="ml-2">
|
||||
{{ selection.name }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div>
|
||||
<div class="mb-2"
|
||||
v-for="action in crmData.actions"
|
||||
v-if="action.changeable === true">
|
||||
<label class="inline-flex items-center" :class="{ 'opacity-50': !action.changeable}">
|
||||
<input type="checkbox"
|
||||
class="border-zinc-400 focus:border-zinc-800 ring-0 focus:ring-0 text-ep-secondary sbw:text-sbw-secondary"
|
||||
v-model="action.value"
|
||||
:disabled="!action.changeable"/>
|
||||
<span class="ml-2">
|
||||
{{ action.name }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="button bg-button" :disabled="loading">
|
||||
Speichern
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapMutations } from 'vuex'
|
||||
import { defaultOptions, checkStatus } from './api'
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
crmData: [],
|
||||
hiddenSelectionGroups: [ 10 ],
|
||||
hiddenSelections: [ 1070 ]
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.beginLoading()
|
||||
|
||||
const options = {
|
||||
...defaultOptions
|
||||
}
|
||||
|
||||
return fetch(`${this.config.myEpApiBaseUrl}/api/crm-selections`, options)
|
||||
.then(checkStatus)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
this.crmData = data
|
||||
})
|
||||
.catch(() => {
|
||||
this.alert({
|
||||
message: 'Es ist ein Fehler aufgetreten :(',
|
||||
success: false,
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
this.endLoading()
|
||||
this.$emit('loaded')
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(['beginLoading', 'endLoading', 'alert']),
|
||||
save () {
|
||||
this.beginLoading()
|
||||
|
||||
const options = {
|
||||
...defaultOptions,
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(this.crmData)
|
||||
}
|
||||
|
||||
return fetch(`${this.config.myEpApiBaseUrl}/api/crm-selections`, options)
|
||||
.then(checkStatus)
|
||||
.then(response => response.json())
|
||||
.then(() => {
|
||||
this.alert({
|
||||
message: 'Die Änderungen wurden gespeichert',
|
||||
success: true,
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
this.alert({
|
||||
message: 'Es ist ein Fehler aufgetreten :(',
|
||||
success: false,
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
this.endLoading()
|
||||
this.$emit('loaded')
|
||||
})
|
||||
},
|
||||
hasVisibleSelections (group) {
|
||||
if (this.hiddenSelectionGroups.indexOf(group.id) !== -1) {
|
||||
return false
|
||||
}
|
||||
for (let selection of group.selections) {
|
||||
if (this.hiddenSelections.indexOf(selection.id) === -1 && selection.changeable === true) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
},
|
||||
isSelectionVisible (selection) {
|
||||
return this.hiddenSelections.indexOf(selection.id) === -1
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['config', 'loading'])
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,157 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1 class="mb-4">
|
||||
Meine Buchungen
|
||||
</h1>
|
||||
<div class="w-full max-w-full overflow-x-scroll md:overflow-x-auto">
|
||||
<table class="min-w-full border border-zinc-200 border-collapse mb-8 text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="px-2 py-1 border border-zinc-200 text-left">
|
||||
Buchungsdatum
|
||||
</th>
|
||||
<th class="px-2 py-1 border border-zinc-200 text-left">
|
||||
Reisedatum
|
||||
</th>
|
||||
<th class="px-2 py-1 border border-zinc-200 text-left">
|
||||
Reise
|
||||
</th>
|
||||
<th class="px-2 py-1 border border-zinc-200 text-left">
|
||||
Vorgang
|
||||
</th>
|
||||
<th class="px-2 py-1 border border-zinc-200 text-center">
|
||||
Preis
|
||||
</th>
|
||||
<th class="px-2 py-1 border border-zinc-200 text-center">
|
||||
offen
|
||||
</th>
|
||||
<th class="px-2 py-1 border border-zinc-200 text-left">
|
||||
Status
|
||||
</th>
|
||||
<th class="px-2 py-1 border border-zinc-200"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="event of events" class="even:bg-zinc-50">
|
||||
<td class="px-2 py-1 border border-zinc-200">
|
||||
{{ formatBookingDate(event.dateBooking) }}
|
||||
</td>
|
||||
<td class="px-2 py-1 border border-zinc-200">
|
||||
{{ event.dateTravel | date }}
|
||||
</td>
|
||||
<td class="px-2 py-1 border border-zinc-200">
|
||||
{{ event.product }}
|
||||
</td>
|
||||
<td class="px-2 py-1 border border-zinc-200">
|
||||
{{ event.bookingNumber }}
|
||||
</td>
|
||||
<td class="px-2 py-1 border border-zinc-200 whitespace-nowrap">
|
||||
{{ formatCurrency(event.price) }}
|
||||
</td>
|
||||
<td class="px-2 py-1 border border-zinc-200 whitespace-nowrap" :class="{ 'text-red-500': hasOutstandingPayment(event) }">
|
||||
{{ calculateBalance(event) }}
|
||||
</td>
|
||||
<td class="px-2 py-1 border border-zinc-200">
|
||||
{{ status[event.status] }}
|
||||
</td>
|
||||
<td class="px-2 py-1 border border-zinc-200">
|
||||
<div class="grid grid-cols-1 xl:grid-cols-3 gap-2 w-32 xl:w-auto">
|
||||
<a :href="getDownloadUrl(event, 'confirmation')" target="_blank" class="button bg-button button--small">
|
||||
Bestätigung
|
||||
</a>
|
||||
<router-link :to="{ name: 'bookingEdit', params: { id: event.id } }" v-if="event.editable" class="button bg-button button--small">
|
||||
bearbeiten
|
||||
</router-link>
|
||||
<a :href="event.travelInfoUrl"
|
||||
target="_blank"
|
||||
class="button bg-button bg-button--secondary button--small"
|
||||
v-if="event.travelInfoUrl">
|
||||
Reiseinfos
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapMutations, mapGetters } from 'vuex'
|
||||
import dayjs from 'dayjs'
|
||||
import 'dayjs/locale/de'
|
||||
import { checkStatus, defaultOptions } from './api'
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
status: {
|
||||
'F': 'Festbuchung',
|
||||
'S': 'Stornierung',
|
||||
'F/S': 'Festbuchung',
|
||||
'O': 'Option',
|
||||
'U': 'Umbuchung',
|
||||
},
|
||||
events: []
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.beginLoading();
|
||||
|
||||
const options = {
|
||||
...defaultOptions
|
||||
}
|
||||
|
||||
return fetch(`${this.config.myEpApiBaseUrl}/api/events`, options)
|
||||
.then(checkStatus)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
this.events = data
|
||||
})
|
||||
.catch(() => {
|
||||
this.alert({
|
||||
message: 'Es ist ein Fehler aufgetreten :(',
|
||||
success: false,
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
this.endLoading()
|
||||
this.$emit('loaded')
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(['beginLoading', 'endLoading', 'alert']),
|
||||
formatBookingDate (date) {
|
||||
if (!date) {
|
||||
return '-'
|
||||
}
|
||||
return dayjs(date).format('DD.MM.YY')
|
||||
},
|
||||
getDownloadUrl (event, type) {
|
||||
return `${this.config.myEpApiBaseUrl}/api/events/${event.id}/${type}`
|
||||
},
|
||||
calculateBalance (event) {
|
||||
if (!event.price) {
|
||||
return '-';
|
||||
}
|
||||
const balance = event.price - event.payment;
|
||||
if (balance > 0) {
|
||||
return this.formatCurrency(balance);
|
||||
}
|
||||
|
||||
return '-';
|
||||
},
|
||||
hasOutstandingPayment (event) {
|
||||
return (event.price - event.payment) > 0;
|
||||
},
|
||||
formatCurrency (value) {
|
||||
return value.toFixed(2).replace('.', ',') + ' €';
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['config', 'loading']),
|
||||
...mapGetters(['isGroupManager'])
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,152 +0,0 @@
|
||||
<template>
|
||||
<div id="myep-main">
|
||||
<h1 class="mb-4">
|
||||
Login MyE&P
|
||||
</h1>
|
||||
<form @submit.prevent="submit()">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
<div>
|
||||
<form-group label="E-Mail" :error="formErrors.email">
|
||||
<input type="email" v-model="email" class="border-zinc-400 focus:border-zinc-800 ring-0 focus:ring-0 mt-1 w-full">
|
||||
</form-group>
|
||||
<form-group label="Passwort" v-if="loginMode">
|
||||
<input type="password" v-model="password" class="border-zinc-400 focus:border-zinc-800 ring-0 focus:ring-0 mt-1 w-full">
|
||||
</form-group>
|
||||
<div class="flex flex-col lg:flex-row space-y-2 lg:space-y-0 lg:space-x-2 mt-4">
|
||||
<button type="submit" class="button bg-button" :disabled="loading">
|
||||
{{ loginMode ? 'Login' : 'Passwort erneuern' }}
|
||||
</button>
|
||||
<button class="button bg-button bg-button--secondary" @click.prevent="toggleMode()">
|
||||
{{ loginMode ? 'Passwort vergessen?' : 'Zum Login' }}
|
||||
</button>
|
||||
<router-link to="register" class="button bg-button bg-button--secondary">Registrieren</router-link>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="mb-2">
|
||||
Neu!
|
||||
</h3>
|
||||
<p class="mb-4">
|
||||
Melde dich jetzt bei MyEP an und du kannst jederzeit auf deine aktuellen Buchungen zugreifen und
|
||||
diese abändern. Egal ob du einen Kurs oder eine Skipasserweiterung hinzufügen/entfernen möchtest,
|
||||
oder das vegetarische Essen auswählen willst. Im neuen Portal ist das nun kostenlos bis 7 Tage vor
|
||||
Reisebeginn möglich!
|
||||
</p>
|
||||
<h3 class="mb-2">
|
||||
Noch keine Zugangsdaten?
|
||||
</h3>
|
||||
<p class="mb-2">
|
||||
Dann gib deine E-Mail ein, mit der Du gebucht hast und klicke auf "Passwort vergessen"! Und wir
|
||||
senden dir ein E-Mail mit einem Passwort-Link!
|
||||
</p>
|
||||
<p>
|
||||
Ein LogIn ist nur möglich wenn Eure Daten schon durch eine Buchung oder Anfrage bei uns
|
||||
angelegt wurden. Wenn Ihr auch Kunde werden möchtet, dann schickt uns Eure Interessen über unser
|
||||
<a href="https://www.ep-reisen.de/skireisen/unternehmen/kontakt/kontaktformular/" title="Kontaktformular">Kontaktformular</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'
|
||||
import FormGroup from './components/FormGroup'
|
||||
|
||||
export default {
|
||||
components: {FormGroup},
|
||||
data () {
|
||||
return {
|
||||
mode: 'login',
|
||||
email: '',
|
||||
password: '',
|
||||
formErrors: {}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(['alert']),
|
||||
...mapActions(['login', 'resetPassword', 'loadCountries']),
|
||||
toggleMode () {
|
||||
this.mode = this.mode === 'login' ? 'reset' : 'login';
|
||||
this.password = ''
|
||||
},
|
||||
submit () {
|
||||
this.message = '';
|
||||
if (this.mode === 'login') {
|
||||
this.performLogin()
|
||||
} else {
|
||||
this.performPasswordReset()
|
||||
}
|
||||
},
|
||||
performLogin () {
|
||||
this.formErrors = {};
|
||||
if (!this.email || !this.password) {
|
||||
this.alert({
|
||||
message: 'Bitte E-Mail und Passwort eingeben',
|
||||
success: false,
|
||||
});
|
||||
return
|
||||
}
|
||||
this.login({
|
||||
email: this.email,
|
||||
password: this.password
|
||||
}).then(() => {
|
||||
this.loadCountries()
|
||||
.then(() => {
|
||||
this.$router.push({ name: 'address' })
|
||||
})
|
||||
}).catch(error => {
|
||||
}).finally(() => {
|
||||
this.email = ''
|
||||
this.password = ''
|
||||
})
|
||||
},
|
||||
performPasswordReset () {
|
||||
this.formErrors = {};
|
||||
if (!this.email) {
|
||||
this.alert({
|
||||
message: 'Bitte die E-Mail Adresse angeben',
|
||||
success: false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.resetPassword(this.email)
|
||||
.then(response => {
|
||||
this.email = '';
|
||||
if (response.success) {
|
||||
this.alert({
|
||||
message: 'Du erhältst in Kürze eine E-Mail mit einem Link zum (Zurück)setzen deines Passworts.',
|
||||
success: true,
|
||||
})
|
||||
} else {
|
||||
this.alert({
|
||||
message: 'Diese E-Mail Adresse konnte nicht gefunden werden.',
|
||||
success: false,
|
||||
})
|
||||
}
|
||||
}).catch(error => {
|
||||
this.alert({
|
||||
message: 'Bitte überprüfe deine Eingaben.',
|
||||
success: false,
|
||||
})
|
||||
const violations = error.response.data.violations;
|
||||
if (typeof violations !== 'undefined') {
|
||||
for (let violation of violations) {
|
||||
this.$set(this.formErrors, violation.property_path, violation.message);
|
||||
}
|
||||
}
|
||||
}).finally(() => {
|
||||
this.$emit('loaded')
|
||||
})
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['loading']),
|
||||
...mapGetters(['isLoggedIn']),
|
||||
loginMode () {
|
||||
return this.mode === 'login';
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,71 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1 class="mb-4">
|
||||
Newsletter
|
||||
</h1>
|
||||
<p class="mb-8" v-html="statusMessage"></p>
|
||||
<button type="submit" class="button bg-button" :disabled="loading" @click.prevent="toggleReqistration()">
|
||||
{{ userData.newsletter ? 'Abmelden' : 'Anmelden' }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapMutations } from 'vuex'
|
||||
import { checkStatus, defaultOptions } from './api'
|
||||
|
||||
export default {
|
||||
methods: {
|
||||
...mapMutations(['beginLoading', 'endLoading', 'alert']),
|
||||
toggleReqistration () {
|
||||
this.userData.newsletter = !this.userData.newsletter;
|
||||
|
||||
const data = {
|
||||
id: this.userData.id,
|
||||
email: this.userData.email,
|
||||
registration: this.userData.newsletter
|
||||
};
|
||||
|
||||
this.beginLoading();
|
||||
|
||||
const options = {
|
||||
...defaultOptions,
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(data)
|
||||
}
|
||||
|
||||
return fetch(`${this.config.myEpApiBaseUrl}/api/newsletter`, options)
|
||||
.then(checkStatus)
|
||||
.then(response => response.json())
|
||||
.then(() => {
|
||||
this.alert({
|
||||
message: 'Die Änderungen wurden gespeichert',
|
||||
success: true,
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
this.alert({
|
||||
message: 'Es ist ein Fehler aufgetreten :(',
|
||||
success: false,
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
this.endLoading()
|
||||
this.$emit('loaded')
|
||||
})
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['config', 'loading', 'userData']),
|
||||
statusMessage () {
|
||||
let message = 'Du bist aktuell';
|
||||
message += this.userData.newsletter ? ' ' : ' <strong>nicht</strong> ';
|
||||
message += 'zum Newsletter angemeldet.';
|
||||
return message;
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.$emit('loaded')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,94 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1 class="mb-4">
|
||||
Registrierung MyE&P
|
||||
</h1>
|
||||
<form @submit.prevent="performRegistration()">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-4 mb-4">
|
||||
<form-group label="Vorname" :error="formErrors.firstName">
|
||||
<input type="text" v-model="userData.firstName" class="border-zinc-400 focus:border-zinc-800 ring-0 focus:ring-0 mt-1 w-full">
|
||||
</form-group>
|
||||
<form-group label="Name" :error="formErrors.lastName">
|
||||
<input type="text" v-model="userData.lastName" class="border-zinc-400 focus:border-zinc-800 ring-0 focus:ring-0 mt-1 w-full">
|
||||
</form-group>
|
||||
<form-group label="E-Mail" :error="formErrors.email">
|
||||
<input type="text" v-model="userData.email" class="border-zinc-400 focus:border-zinc-800 ring-0 focus:ring-0 mt-1 w-full">
|
||||
</form-group>
|
||||
<form-group label="Gender" :error="formErrors.gender">
|
||||
<select v-model="userData.gender" class="border-zinc-400 focus:border-zinc-800 ring-0 focus:ring-0 mt-1 w-full">
|
||||
<option value="M">M</option>
|
||||
<option value="W">W</option>
|
||||
<option value="D">D</option>
|
||||
</select>
|
||||
</form-group>
|
||||
</div>
|
||||
<div class="flex flex-col lg:flex-row space-y-2 lg:space-y-0 lg:space-x-2">
|
||||
<button type="submit" class="button bg-button" :disabled="loading">
|
||||
Registrieren
|
||||
</button>
|
||||
<router-link to="login" class="button bg-button bg-button--secondary">
|
||||
Zum Login
|
||||
</router-link>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
w
|
||||
<script>
|
||||
import { mapState, mapActions, mapMutations } from 'vuex'
|
||||
import FormGroup from './components/FormGroup'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FormGroup
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
userData: {
|
||||
lastName: '',
|
||||
firstName: '',
|
||||
email: '',
|
||||
gender: 'M'
|
||||
},
|
||||
formErrors: {}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(['alert']),
|
||||
...mapActions(['register']),
|
||||
performRegistration () {
|
||||
this.message = '';
|
||||
this.formErrors = {};
|
||||
this.register(this.userData)
|
||||
.then(response => {
|
||||
if (response.success) {
|
||||
this.alert({
|
||||
message: 'Du erhältst in Kürze eine E-Mail mit einem Link zum (Zurück)setzen deines Passworts.',
|
||||
success: true,
|
||||
});
|
||||
this.userData = {
|
||||
lastName: '',
|
||||
firstName: '',
|
||||
email: '',
|
||||
gender: 'M'
|
||||
};
|
||||
this.formErrors = {};
|
||||
} else {
|
||||
this.alert({
|
||||
message: 'Du bist bereits registriert. Bitte nutze die Passwort vergessen Funktion.',
|
||||
success: false,
|
||||
});
|
||||
}
|
||||
}).catch(error => {
|
||||
const violations = error.response.violations;
|
||||
for (let violation of violations) {
|
||||
this.$set(this.formErrors, violation.property_path, violation.message);
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['loading'])
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,17 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<p class="text-2xl pb-4">Teamer</p>
|
||||
<div class="p-4 mb-8 text-white bg-ep-primary">
|
||||
Ab sofort nutzen wir zur Teameinteilung ein neues Tool. Den Zugang erhältst du hier: <a class="underline" href="https://myep-team.ep-reisen.de/" target="_blank">https://myep-team.ep-reisen.de/</a>
|
||||
<br>
|
||||
Du kannst dich mit deinen bisherigen Log-In Daten anmelden und schonmal ein wenig zurechtfinden.
|
||||
<br>
|
||||
Ein detailliertes Tutorial folgt noch.
|
||||
<br>
|
||||
Wir freuen uns, dich in der neuen Saison einteilen zu dürfen 😊
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
</script>
|
||||
@@ -1,24 +0,0 @@
|
||||
const defaultOptions = {
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
credentials: 'include',
|
||||
}
|
||||
|
||||
function checkStatus(response) {
|
||||
if (response.ok) {
|
||||
return response
|
||||
} else {
|
||||
return response
|
||||
.json()
|
||||
.then(error => {
|
||||
const responseError = new Error(error.message)
|
||||
if (error.hasOwnProperty('violations')) {
|
||||
responseError.violations = error.violations
|
||||
}
|
||||
throw responseError
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export { checkStatus, defaultOptions }
|
||||
-61
@@ -1,61 +0,0 @@
|
||||
<template>
|
||||
<div class="relative" v-if="mutable && selectableAccommodations.length > 0">
|
||||
<button class="button bg-button button--small w-full"
|
||||
type="button"
|
||||
@click.prevent="visible = !visible">
|
||||
<span>
|
||||
Zimmer
|
||||
</span>
|
||||
<svg class="w-4 h-4" fill="none" :class="{ 'rotate-180': visible }" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg>
|
||||
</button>
|
||||
<ul class="absolute top-100 left-0 z-50 mt-4 border shadow-md p-4 bg-white w-screen max-w-xs divide-y" v-show="visible">
|
||||
<li v-for="accommodation in selectableAccommodations" :key="accommodation.id" class="py-1">
|
||||
<a href="#" @click.prevent="selectAccommodation(accommodation)">
|
||||
{{ accommodation.name}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'AccommodationSelect',
|
||||
props: {
|
||||
participant: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
contingents: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
mutable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
selectAccommodation (accommodation) {
|
||||
this.$emit('selected', accommodation);
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
selectableAccommodations () {
|
||||
let accommodations = []
|
||||
for (let accommodationId of Object.keys(this.contingents)) {
|
||||
if (this.participant.accommodationId !== parseInt(accommodationId)) {
|
||||
accommodations.push(this.contingents[accommodationId])
|
||||
}
|
||||
}
|
||||
this.visible = false
|
||||
return accommodations
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
-41
@@ -1,41 +0,0 @@
|
||||
<template>
|
||||
<div class="border border-zinc-200 rounded mb-4">
|
||||
<div class="bg-zinc-50 p-2">
|
||||
<h3 class="text-base font-bold">
|
||||
<a role="button" href="#" class="flex items-center justify-between"
|
||||
@click.prevent="expanded = !expanded">
|
||||
<span>{{ title }}</span>
|
||||
<svg class="w-6 h-6" fill="none" :class="{ 'rotate-180': expanded }" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg>
|
||||
</a>
|
||||
</h3>
|
||||
</div>
|
||||
<div :class="{ 'hidden': !expanded }">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'CollapsiblePanel',
|
||||
data() {
|
||||
return {
|
||||
id: null,
|
||||
expanded: false,
|
||||
}
|
||||
},
|
||||
props: {
|
||||
parent: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.id = this._uid
|
||||
}
|
||||
}
|
||||
</script>
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
<template>
|
||||
<panel title="Gebuchte Kontingente">
|
||||
<ul class="list-none">
|
||||
<li class="p-2 border-b border-zinc-200 last:border-0" v-for="contingent in contingents" :key="contingent.id">
|
||||
{{ contingent.room }}: {{ contingent.count }}
|
||||
</li>
|
||||
</ul>
|
||||
</panel>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Panel from './Panel'
|
||||
|
||||
export default {
|
||||
name: 'ContingentsOverview',
|
||||
components: {
|
||||
Panel
|
||||
},
|
||||
props: {
|
||||
contingents: {
|
||||
type: Array,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
<template>
|
||||
<div class="mb-4">
|
||||
<label class="block mb-1" :class="{ 'text-red-500': error }">
|
||||
{{ label }}:
|
||||
</label>
|
||||
<slot></slot>
|
||||
<span class="block text-sm mt-1 text-red-500" v-show="error">
|
||||
{{ error }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'FormGroup',
|
||||
props: {
|
||||
label: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
error: {
|
||||
type: String,
|
||||
default: null
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
<template>
|
||||
<panel title="Änderungen möglich bis">
|
||||
<ul class="list-none">
|
||||
<li class="p-2 border-b border-zinc-200 last:border-0" v-for="field in fields" :key="field.prop">
|
||||
<strong>
|
||||
{{ field.label }}:
|
||||
</strong>
|
||||
<span class="px-2 py-1 text-white" :class="mutableFields[field.prop].mutable ? 'bg-emerald-500' : 'bg-red-500'">
|
||||
{{ mutableFields[field.prop].mutable ? mutableFields[field.prop].until : 'keine Änderung möglich' }}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</panel>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Panel from './Panel';
|
||||
|
||||
export default {
|
||||
name: 'MutableFields',
|
||||
data() {
|
||||
return {
|
||||
fields: [
|
||||
{ prop: 'accommodation', label: 'Unterkunft' },
|
||||
{ prop: 'participantData', label: 'Teilnehmer' },
|
||||
{ prop: 'services', label: 'Leistungen' },
|
||||
{ prop: 'transportation', label: 'Beförderung' },
|
||||
{ prop: 'pickup', label: 'Zustiege' },
|
||||
]
|
||||
}
|
||||
},
|
||||
components: {
|
||||
Panel
|
||||
},
|
||||
props: {
|
||||
mutableFields: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,33 +0,0 @@
|
||||
<template>
|
||||
<div class="border border-zinc-50 rounded mb-4">
|
||||
<div class="bg-zinc-50 p-2">
|
||||
<h3 class="text-base font-bold">
|
||||
{{ title }}
|
||||
</h3>
|
||||
</div>
|
||||
<template v-if="body">
|
||||
<div class="bg-white p-2">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<slot></slot>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Panel',
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
body: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
-53
@@ -1,53 +0,0 @@
|
||||
<template>
|
||||
<div class="relative" ref="target">
|
||||
<button class="button bg-button button--small w-full"
|
||||
type="button"
|
||||
@click.prevent="visible = !visible">
|
||||
<span>
|
||||
Telefon
|
||||
</span>
|
||||
<svg class="w-4 h-4" fill="none" :class="{ 'rotate-180': visible }" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg>
|
||||
</button>
|
||||
<div class="absolute top-100 left-0 z-50 mt-4 border shadow-md p-4 bg-white w-screen max-w-xs space-y-2" v-show="visible">
|
||||
<input class="border-zinc-400 focus:border-zinc-800 ring-0 focus:ring-0 mt-1 w-full"
|
||||
:class="{'cursor-not-allowed': !mutable }"
|
||||
type="text" placeholder="Mobil"
|
||||
v-model="participant.data.phoneMobile" :disabled="!mutable"
|
||||
@change="$emit('updated', participant)"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ParticipantAddressForm',
|
||||
props: {
|
||||
participant: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
countries: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
mutable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
visible: false,
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
let self = this
|
||||
document.addEventListener('click', e => {
|
||||
if (self.$refs.target !== undefined && self.$refs.target.contains(e.target) === false) {
|
||||
self.visible = false
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
-66
@@ -1,66 +0,0 @@
|
||||
<template>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-6 gap-1">
|
||||
<input class="border-zinc-400 focus:border-zinc-800 ring-0 focus:ring-0 mt-1 w-full"
|
||||
:class="{'cursor-not-allowed': !mutable }"
|
||||
type="text" placeholder="Nachname"
|
||||
v-model="participant.data.lastName" :disabled="!mutable"
|
||||
@change="$emit('updated', participant)"
|
||||
>
|
||||
<input class="border-zinc-400 focus:border-zinc-800 ring-0 focus:ring-0 mt-1 w-full"
|
||||
:class="{'cursor-not-allowed': !mutable }"
|
||||
type="text" placeholder="Vorname"
|
||||
v-model="participant.data.firstName" :disabled="!mutable"
|
||||
@change="$emit('updated', participant)"
|
||||
>
|
||||
<input class="border-zinc-400 focus:border-zinc-800 ring-0 focus:ring-0 mt-1 w-full"
|
||||
:class="{'cursor-not-allowed': !mutable }"
|
||||
type="email" placeholder="E-Mail"
|
||||
v-model="participant.data.email" :disabled="!mutable"
|
||||
@change="$emit('updated', participant)"
|
||||
>
|
||||
<select class="border-zinc-400 focus:border-zinc-800 ring-0 focus:ring-0 mt-1 w-full"
|
||||
:class="{'cursor-not-allowed': !mutable }"
|
||||
v-model="participant.data.gender" :disabled="!mutable"
|
||||
@change="$emit('updated', participant)"
|
||||
>
|
||||
<option value="W">weiblich</option>
|
||||
<option value="M">männlich</option>
|
||||
<option value="D">divers</option>
|
||||
</select>
|
||||
<select class="border-zinc-400 focus:border-zinc-800 ring-0 focus:ring-0 mt-1 w-full"
|
||||
:class="{'cursor-not-allowed': !mutable }"
|
||||
v-model="participant.data.nationality" :disabled="!mutable"
|
||||
@change="$emit('updated', participant)"
|
||||
>
|
||||
<option value="">Nationalität...</option>
|
||||
<option v-for="country in countries" :key="country.code" :value="country.code">{{ country.adjective }}</option>
|
||||
</select>
|
||||
<input type="date"
|
||||
class="border-zinc-400 focus:border-zinc-800 ring-0 focus:ring-0 mt-1 w-full"
|
||||
:class="{'cursor-not-allowed': !mutable }"
|
||||
placeholder="Geburtsdatum"
|
||||
v-model="participant.data.dateOfBirth" :disabled="!mutable"
|
||||
@change="$emit('updated', participant)"
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ParticipantDataForm',
|
||||
props: {
|
||||
participant: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
countries: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
mutable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
-87
@@ -1,87 +0,0 @@
|
||||
<template>
|
||||
<div class="relative" v-if="hasSelectedRentals" ref="target">
|
||||
<button class="button bg-button button--small" @click.prevent="visible = !visible">
|
||||
<span>
|
||||
Größen
|
||||
</span>
|
||||
<svg class="w-4 h-4" fill="none" :class="{ 'rotate-180': visible }" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg>
|
||||
</button>
|
||||
<div class="absolute top-100 left-0 z-50 mt-4 border shadow-md p-4 bg-white w-screen max-w-xs space-y-2" v-show="visible">
|
||||
<input class="form-input w-full" type="number" placeholder="Körpergröße (150-210cm)"
|
||||
v-model.number="participant.data.height" :disabled="!mutable"
|
||||
@change="validate(participant.data.height, 150, 210, 'Körpergröße')"
|
||||
>
|
||||
<input class="form-input w-full" type="number" placeholder="Schuhgröße (36-49, EU-Größe)"
|
||||
v-model.number="participant.data.shoeSize" :disabled="!mutable"
|
||||
@change="validate(participant.data.shoeSize, 36, 49, 'Schuhgröße')"
|
||||
>
|
||||
<input class="form-input w-full" type="number" placeholder="Gewicht (40 - 130kg)"
|
||||
v-model.number="participant.data.weight" :disabled="!mutable"
|
||||
@change="validate(participant.data.weight, 40, 130, 'Gewicht')"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ParticipantSizesForm',
|
||||
props: {
|
||||
participant: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
services: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
subTypes: {
|
||||
type: Array,
|
||||
default() {
|
||||
return ['VER', 'VE2', 'VE3', 'VE4', 'VE5', 'VE6', 'VE7', 'VE8']
|
||||
}
|
||||
},
|
||||
mutable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
visible: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
validate (value, min, max, label) {
|
||||
let number = parseInt(value)
|
||||
if (isNaN(number) || number < min || number > max) {
|
||||
alert(`Bitte einen Wert zwischen ${min} und ${max} für ${label} eingeben`)
|
||||
}
|
||||
|
||||
this.$emit('updated', this.participant)
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
hasSelectedRentals () {
|
||||
let result = false;
|
||||
for (let id of this.participant.serviceIds) {
|
||||
if (typeof this.services[id] === 'undefined') {
|
||||
continue;
|
||||
}
|
||||
if (this.subTypes.includes(this.services[id].subType)) {
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
let self = this
|
||||
document.addEventListener('click', e => {
|
||||
if (self.$refs.target !== undefined && self.$refs.target.contains(e.target) === false) {
|
||||
self.visible = false
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
-66
@@ -1,66 +0,0 @@
|
||||
<template>
|
||||
<div class="relative" ref="target" v-show="hasSelectedBus">
|
||||
<button class="button bg-button button--small" @click.prevent="visible = !visible">
|
||||
<span>
|
||||
Zustieg
|
||||
</span>
|
||||
<svg class="w-4 h-4" fill="none" :class="{ 'rotate-180': visible }" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg>
|
||||
</button>
|
||||
<div class="absolute top-100 left-0 z-50 mt-4 border shadow-md p-4 bg-white w-screen max-w-xs" v-show="visible">
|
||||
<div class="py-1" v-for="pickup in pickups" :key="pickup.id">
|
||||
<label class="inline-flex items-center text-sm">
|
||||
<input type="radio"
|
||||
class="border-zinc-400 focus:border-zinc-800 ring-0 focus:ring-0 text-ep-secondary sbw:text-sbw-secondary"
|
||||
:name="'pickup-' + participant.data.id"
|
||||
v-model="pickupId"
|
||||
:value="pickup.busproId"
|
||||
:disabled="!mutable"
|
||||
@change="$emit('selected', pickupId)"
|
||||
>
|
||||
<span class="ml-2">{{ pickup.city }} ({{ pickup.street }})</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'PickupSelect',
|
||||
props: {
|
||||
pickups: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
participant: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
mutable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
visible: false,
|
||||
pickupId: this.participant.pickupId
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
hasSelectedBus () {
|
||||
return this.participant.transportations.some((transportation) => {
|
||||
return 'BUS' === transportation.subType && 'HIN' === transportation.direction
|
||||
})
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
let self = this
|
||||
document.addEventListener('click', e => {
|
||||
if (self.$refs.target !== undefined && self.$refs.target.contains(e.target) === false) {
|
||||
self.visible = false
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
-98
@@ -1,98 +0,0 @@
|
||||
<template>
|
||||
<div class="relative" v-if="hasServices" ref="target">
|
||||
<button class="button bg-button button--small" type="button" @click.prevent="visible = !visible">
|
||||
<span>
|
||||
{{ subType.label }}
|
||||
</span>
|
||||
<svg class="w-4 h-4" fill="none" :class="{ 'rotate-180': visible }" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg>
|
||||
</button>
|
||||
<div class="absolute top-100 left-0 z-50 mt-1 border shadow-md p-4 bg-white w-screen max-w-xs" v-show="visible">
|
||||
<div class="text-right">
|
||||
<button type="button" @click="visible = false">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="py-1" v-for="service in availableServices" :key="service.id">
|
||||
<label class="inline-flex items-center text-sm">
|
||||
<input type="checkbox"
|
||||
class="border-zinc-400 focus:border-zinc-800 ring-0 focus:ring-0 text-ep-secondary sbw:text-sbw-secondary"
|
||||
v-model="participant.serviceIds"
|
||||
:value="service.id"
|
||||
:disabled="false === isServiceSelectable(service)"
|
||||
@change="selectService(service, $event)"
|
||||
>
|
||||
<span class="ml-2">{{ service.name }} ({{ service.price }}€)</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ServiceSelect',
|
||||
props: {
|
||||
subType: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
services: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
participant: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
mutable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
visible: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
selectService(service, event) {
|
||||
const action = event.target.checked ? 'selected' : 'unselected';
|
||||
this.$emit('updated', { action, participant: this.participant, service });
|
||||
},
|
||||
isServiceSelectable(service) {
|
||||
// Service is mandatory so check current selection state
|
||||
if (service.mandatory) {
|
||||
return this.participant.serviceIds.indexOf(service.id) === -1
|
||||
}
|
||||
// Service is mutable and available
|
||||
return true === this.mutable && service.available > 0;
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
availableServices () {
|
||||
let filteredServices = {};
|
||||
for (let id of Object.keys(this.services)) {
|
||||
if (this.services[id].subType === undefined) {
|
||||
continue;
|
||||
}
|
||||
if (!this.subType.codes.includes(this.services[id].subType)) {
|
||||
continue;
|
||||
}
|
||||
filteredServices[id] = this.services[id];
|
||||
}
|
||||
return filteredServices;
|
||||
},
|
||||
hasServices () {
|
||||
return Object.keys(this.availableServices).length > 0;
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
let self = this
|
||||
document.addEventListener('click', e => {
|
||||
if (self.$refs.target !== undefined && self.$refs.target.contains(e.target) === false) {
|
||||
self.visible = false
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
-57
@@ -1,57 +0,0 @@
|
||||
<template>
|
||||
<panel title="Gebuchte Zusatzleistungen">
|
||||
<ul class="list-none" v-show="hasServiceAssignments">
|
||||
<li class="p-2 border-b border-zinc-200 last:border-0" v-for="(assignment, serviceId) in serviceAssignments">
|
||||
{{ services[serviceId].name }}: {{ assignment.length }}
|
||||
</li>
|
||||
</ul>
|
||||
<div class="bg-white p-2" v-show="!hasServiceAssignments">
|
||||
<span>keine</span>
|
||||
</div>
|
||||
</panel>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Panel from './Panel'
|
||||
|
||||
export default {
|
||||
name: 'ServicesOverview',
|
||||
components: {
|
||||
Panel
|
||||
},
|
||||
props: {
|
||||
services: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
participantData: {
|
||||
type: Array,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
serviceAssignments () {
|
||||
let assignments = {};
|
||||
for (let participant of this.participantData) {
|
||||
for (let id of participant.serviceIds) {
|
||||
if (typeof this.services[id] === 'undefined') {
|
||||
continue;
|
||||
}
|
||||
if (!assignments.hasOwnProperty(id)) {
|
||||
assignments[id] = [];
|
||||
}
|
||||
assignments[id].push(participant.data.id);
|
||||
}
|
||||
}
|
||||
return assignments;
|
||||
},
|
||||
hasServiceAssignments() {
|
||||
return Object.keys(this.serviceAssignments).length > 0 && this.serviceAssignments.constructor === Object
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
-76
@@ -1,76 +0,0 @@
|
||||
<template>
|
||||
<div class="relative" ref="target">
|
||||
<button class="button bg-button button--small" @click.prevent="visible = !visible">
|
||||
<span>
|
||||
Anreise
|
||||
</span>
|
||||
<svg class="w-4 h-4" fill="none" :class="{ 'rotate-180': visible }" stroke="currentColor"
|
||||
viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="absolute top-100 left-0 z-50 mt-4 border shadow-md p-4 bg-white w-screen max-w-xs" v-show="visible">
|
||||
<div class="flex flex-col space-y-2" v-if="hasServices">
|
||||
<label class="flex items-center"
|
||||
v-for="service in services" :key="service.id">
|
||||
<input type="checkbox"
|
||||
class="border-zinc-400 focus:border-zinc-800 ring-0 focus:ring-0 text-ep-secondary sbw:text-sbw-secondary"
|
||||
v-model="selectedTransportationIds"
|
||||
:value="service.id"
|
||||
:disabled="isServiceUnavailable(service) || !mutable"
|
||||
@change="selectService(service, $event)"/>
|
||||
<span class="ml-2">{{ service.name }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'TransportationSelect',
|
||||
props: {
|
||||
services: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
participant: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
mutable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
visible: false,
|
||||
selectedTransportationIds: this.participant.transportations.map((transportation) => transportation.busproId),
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
selectService(service, event) {
|
||||
// uncheck transportation ids with same direction to make them unique
|
||||
let direction = service.direction
|
||||
let serviceIdsToUncheck = Object.entries(this.services).filter(item => direction === item[1].direction && item[1].id !== service.id).map(item => item[1].id)
|
||||
this.selectedTransportationIds = this.selectedTransportationIds.filter(id => !serviceIdsToUncheck.includes(id))
|
||||
this.participant.transportations = this.selectedTransportations
|
||||
|
||||
let action = event.target.checked ? 'select' : 'unselect';
|
||||
this.$emit('updated', { action, service });
|
||||
},
|
||||
isServiceUnavailable(service) {
|
||||
return service.available === 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
hasServices() {
|
||||
return Object.keys(this.services).length > 0;
|
||||
},
|
||||
selectedTransportations() {
|
||||
return this.selectedTransportationIds.map((id) => this.services[id])
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,99 +0,0 @@
|
||||
import Vue from 'vue';
|
||||
import VueRouter from 'vue-router';
|
||||
import store from '../store';
|
||||
|
||||
Vue.use(VueRouter);
|
||||
|
||||
const guard = (to, from, next) => {
|
||||
if (store.getters.isLoggedIn === false) {
|
||||
next({ name: 'login' });
|
||||
} else if (to.meta.requiresTeamer && store.getters.isTeamer === false) {
|
||||
next(false);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
};
|
||||
|
||||
import Address from '../Address'
|
||||
import Events from '../Events'
|
||||
import Login from '../Login'
|
||||
import CrmData from '../CrmData'
|
||||
import Newsletter from '../Newsletter'
|
||||
import Registration from '../Registration'
|
||||
|
||||
// Lazy load some components when they become required
|
||||
const Teamer = () => import('../Teamer')
|
||||
const BookingEdit = () => import('../BookingEdit')
|
||||
|
||||
export default new VueRouter({
|
||||
routes: [
|
||||
{
|
||||
name: 'login',
|
||||
path: '/',
|
||||
component: Login,
|
||||
beforeEnter (from, to, next) {
|
||||
if (store.getters.isLoggedIn === false) {
|
||||
next();
|
||||
} else {
|
||||
next({ name: 'address' });
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'register',
|
||||
path: '/register',
|
||||
component: Registration,
|
||||
beforeEnter (from, to, next) {
|
||||
if (store.getters.isLoggedIn === false) {
|
||||
next();
|
||||
} else {
|
||||
next({ name: 'address' });
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'address',
|
||||
path: '/adressdaten',
|
||||
component: Address,
|
||||
beforeEnter: guard
|
||||
},
|
||||
{
|
||||
name: 'newsletter',
|
||||
path: '/newsletter',
|
||||
component: Newsletter,
|
||||
beforeEnter: guard
|
||||
},
|
||||
{
|
||||
name: 'profile',
|
||||
path: '/profil',
|
||||
component: CrmData,
|
||||
beforeEnter: guard
|
||||
},
|
||||
{
|
||||
name: 'events',
|
||||
path: '/vorgaenge',
|
||||
component: Events,
|
||||
beforeEnter: guard,
|
||||
},
|
||||
{
|
||||
name: 'bookingEdit',
|
||||
path: '/buchung/:id',
|
||||
component: BookingEdit,
|
||||
beforeEnter: guard
|
||||
},
|
||||
{
|
||||
name: 'teamer',
|
||||
path: '/teamer',
|
||||
component: Teamer,
|
||||
meta: { requiresTeamer: true },
|
||||
beforeEnter: guard
|
||||
},
|
||||
{
|
||||
path: '*',
|
||||
redirect: { name: 'login' }
|
||||
}
|
||||
],
|
||||
scrollBehavior (to, from, savedPosition) {
|
||||
return { selector: '#myep', offset: { x: 0 , y: 160 } }
|
||||
}
|
||||
});
|
||||
@@ -1,263 +0,0 @@
|
||||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
import { checkStatus, defaultOptions } from '../api'
|
||||
|
||||
const configElement = document.getElementById('appconfig')
|
||||
const config = JSON.parse(configElement.innerHTML)
|
||||
|
||||
Vue.use(Vuex)
|
||||
|
||||
const defaultUserData = {
|
||||
isTeamer: false,
|
||||
isGroupManager: false,
|
||||
email: '',
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
gender: '',
|
||||
dateOfBirth: null,
|
||||
street: '',
|
||||
zipCode: '',
|
||||
city: '',
|
||||
phoneMobile: '',
|
||||
phonePrivate: '',
|
||||
newsletter: false,
|
||||
};
|
||||
|
||||
export default new Vuex.Store({
|
||||
state: {
|
||||
config: {},
|
||||
loading: false,
|
||||
statusMessage: '',
|
||||
authenticated: false,
|
||||
userData: defaultUserData,
|
||||
countries: [],
|
||||
alert: {
|
||||
message: '',
|
||||
success: true,
|
||||
}
|
||||
},
|
||||
mutations: {
|
||||
initConfig(state) {
|
||||
state.config = config
|
||||
},
|
||||
setUserData(state, data) {
|
||||
if (state.userData.watchList) {
|
||||
data.watchList = [...state.userData.watchList, ...data.watchList]
|
||||
}
|
||||
state.userData = data
|
||||
},
|
||||
login(state) {
|
||||
state.authenticated = true
|
||||
},
|
||||
logout(state) {
|
||||
state.userData = defaultUserData;
|
||||
state.authenticated = false
|
||||
},
|
||||
alert(state, payload) {
|
||||
state.alert = payload;
|
||||
},
|
||||
beginLoading(state, statusMessage) {
|
||||
state.loading = true
|
||||
state.statusMessage = statusMessage
|
||||
state.alert = {message: '', success: true}
|
||||
},
|
||||
endLoading(state) {
|
||||
state.loading = false
|
||||
state.statusMessage = ''
|
||||
},
|
||||
setCountries(state, countries) {
|
||||
state.countries = countries;
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
checkLoginState({commit, state}) {
|
||||
commit('beginLoading')
|
||||
|
||||
const options = {
|
||||
...defaultOptions
|
||||
}
|
||||
|
||||
return fetch(`${state.config.myEpApiBaseUrl}/api/ping`, options)
|
||||
.then(checkStatus)
|
||||
.then(response => response.json())
|
||||
.then(() => {
|
||||
commit('login')
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
commit('endLoading')
|
||||
})
|
||||
},
|
||||
loadCountries({commit, state}) {
|
||||
commit('beginLoading')
|
||||
|
||||
const options = {
|
||||
...defaultOptions
|
||||
}
|
||||
|
||||
return fetch(`${state.config.myEpApiBaseUrl}/api/countries`, options)
|
||||
.then(checkStatus)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
commit('setCountries', data)
|
||||
})
|
||||
.catch(error => {
|
||||
commit('alert', {
|
||||
message: 'Es ist ein Fehler aufgetreten: ' + error.message,
|
||||
success: false,
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
commit('endLoading')
|
||||
})
|
||||
},
|
||||
login({commit}, credentials) {
|
||||
commit('beginLoading')
|
||||
|
||||
const options = {
|
||||
...defaultOptions,
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
username: credentials.email,
|
||||
password: credentials.password
|
||||
})
|
||||
}
|
||||
|
||||
return fetch(`${config.myEpApiBaseUrl}/api/login`, options)
|
||||
.then(checkStatus)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
commit('login', data)
|
||||
})
|
||||
.catch(error => {
|
||||
commit('logout')
|
||||
commit('alert', {
|
||||
message: 'Der Login ist fehlgeschlagen :(',
|
||||
success: false,
|
||||
});
|
||||
throw error
|
||||
})
|
||||
.finally(() => {
|
||||
commit('endLoading')
|
||||
})
|
||||
},
|
||||
logout({commit}) {
|
||||
commit('beginLoading')
|
||||
|
||||
const options = {
|
||||
...defaultOptions
|
||||
}
|
||||
|
||||
return fetch(`${config.myEpApiBaseUrl}/api/logout`, options)
|
||||
.then(checkStatus)
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
commit('logout')
|
||||
commit('endLoading')
|
||||
})
|
||||
},
|
||||
resetPassword({commit}, email) {
|
||||
commit('beginLoading')
|
||||
|
||||
const options = {
|
||||
...defaultOptions,
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
email
|
||||
})
|
||||
}
|
||||
|
||||
return fetch(`${config.myEpApiBaseUrl}/api/reset-password`, options)
|
||||
.then(checkStatus)
|
||||
.then(response => response.json())
|
||||
.then(response => response)
|
||||
.catch(error => {
|
||||
commit('alert', {
|
||||
message: 'Es ist ein Fehler aufgetreten: ' + error.message,
|
||||
success: false,
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
commit('endLoading')
|
||||
})
|
||||
},
|
||||
register({commit}, userData) {
|
||||
commit('beginLoading')
|
||||
|
||||
const options = {
|
||||
...defaultOptions,
|
||||
method: 'POST',
|
||||
body: JSON.stringify(userData)
|
||||
}
|
||||
|
||||
return fetch(`${config.myEpApiBaseUrl}/api/register`, options)
|
||||
.then(checkStatus)
|
||||
.then(response => response.json())
|
||||
.then(data => data)
|
||||
.catch(error => {
|
||||
commit('alert', {
|
||||
message: 'Es ist ein Fehler aufgetreten: ' + error.message,
|
||||
success: false,
|
||||
})
|
||||
throw error
|
||||
})
|
||||
.finally(() => {
|
||||
commit('endLoading')
|
||||
})
|
||||
},
|
||||
loadAddress({commit}) {
|
||||
commit('beginLoading')
|
||||
|
||||
const options = {
|
||||
...defaultOptions,
|
||||
}
|
||||
|
||||
return fetch(`${config.myEpApiBaseUrl}/api/address`, options)
|
||||
.then(checkStatus)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
commit('setUserData', data)
|
||||
if (data.watchlist) {
|
||||
commit('setWatchlist', data.watchlist)
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
commit('alert', {
|
||||
message: 'Es ist ein Fehler aufgetreten: ' + error.message,
|
||||
success: false,
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
commit('endLoading')
|
||||
})
|
||||
},
|
||||
saveAddress({commit, state}, data) {
|
||||
commit('beginLoading')
|
||||
|
||||
const options = {
|
||||
...defaultOptions,
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(data)
|
||||
}
|
||||
|
||||
return fetch(`${config.myEpApiBaseUrl}/api/address`, options)
|
||||
.then(checkStatus)
|
||||
.then(response => response.json())
|
||||
.then(() => {
|
||||
commit('alert', {
|
||||
message: 'Die Änderungen wurden gespeichert',
|
||||
success: true,
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
commit('endLoading')
|
||||
})
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
onWatchlist: state => uid => state.userData.watchList.indexOf(uid) !== -1,
|
||||
isLoggedIn: state => !!state.authenticated,
|
||||
isTeamer: state => !!state.userData.isTeamer,
|
||||
isGroupManager: state => !!state.userData.isGroupManager,
|
||||
}
|
||||
});
|
||||
@@ -6,7 +6,7 @@
|
||||
<a href="#main-content" class="hidden lg:block fixed left-0 top-0 bg-ep-primary-dark text-white py-1 px-4 z-50 transform focus:translate-y-12 transition">
|
||||
zum Inhalt springen
|
||||
</a>
|
||||
|
||||
|
||||
<f:if condition="{settings.hideAreaNavigation}">
|
||||
<f:else>
|
||||
<div class="topnav">
|
||||
@@ -37,7 +37,7 @@
|
||||
</div>
|
||||
</f:if>
|
||||
<f:if condition="{settings.myEpPageUid}">
|
||||
<f:link.typolink parameter="{settings.myEpPageUid}" class="topnav__item" title="My E&P">
|
||||
<f:link.typolink parameter="{settings.myEpUrl}" class="topnav__item" title="My E&P">
|
||||
{ep:icon(icon: 'user', class: 'w-5 h-5')}
|
||||
</f:link.typolink>
|
||||
</f:if>
|
||||
|
||||
@@ -9,7 +9,6 @@ Encore
|
||||
.setOutputPath('./public/typo3conf/ext/ep_theme/Resources/Public/')
|
||||
.setPublicPath('/typo3conf/ext/ep_theme/Resources/Public/')
|
||||
.addEntry('main', './public/typo3conf/ext/ep_theme/Resources/Private/Assets/js/main.js')
|
||||
.addEntry('myep', './public/typo3conf/ext/ep_theme/Resources/Private/Assets/js/myep.js')
|
||||
.addEntry('groups-calculator', './public/typo3conf/ext/ep_theme/Resources/Private/Assets/js/groups-calculator.js')
|
||||
.addStyleEntry('rte', './public/typo3conf/ext/ep_theme/Resources/Private/Assets/scss/rte.scss')
|
||||
.splitEntryChunks()
|
||||
|
||||
Reference in New Issue
Block a user