Initial commit

This commit is contained in:
philipp lang 2023-01-07 16:10:18 +01:00
commit 5eeda308ce
20 changed files with 1101 additions and 0 deletions

92
Plugin.php Normal file
View File

@ -0,0 +1,92 @@
<?php
namespace Zoomyboy\Event;
use Backend;
use System\Classes\PluginBase;
/**
* event Plugin Information File.
*/
class Plugin extends PluginBase
{
/**
* Returns information about this plugin.
*
* @return array
*/
public function pluginDetails()
{
return [
'name' => 'event',
'description' => 'No description provided yet...',
'author' => 'zoomyboy',
'icon' => 'icon-leaf',
];
}
/**
* Register method, called when the plugin is first registered.
*
* @return void
*/
public function register()
{
}
/**
* Boot method, called right before the request route.
*
* @return array
*/
public function boot()
{
}
/**
* Registers any front-end components implemented in this plugin.
*
* @return array
*/
public function registerComponents()
{
return [
'Zoomyboy\Event\Components\EventForm' => 'event_form',
];
}
/**
* Registers any back-end permissions used by this plugin.
*
* @return array
*/
public function registerPermissions()
{
return []; // Remove this line to activate
return [
'zoomyboy.event.some_permission' => [
'tab' => 'event',
'label' => 'Some permission',
],
];
}
/**
* Registers back-end navigation items for this plugin.
*
* @return array
*/
public function registerNavigation()
{
return [
'event' => [
'label' => 'Teilnehmer',
'url' => Backend::url('zoomyboy/event/participant'),
'icon' => 'icon-leaf',
'permissions' => ['zoomyboy.event.*'],
'order' => 500,
],
];
}
}

168
assets/eventregistration.js Normal file
View File

@ -0,0 +1,168 @@
import scrollToElement from './scrollToElement.js';
var toastedOptions = {
position: 'top-right',
duration: 3000,
fitToScreen: true,
fullWidth: true,
theme: 'material',
};
export default function (toasted) {
var toasted = new toasted(toastedOptions);
return {
data: {
firstname: 'Philipp',
lastname: 'Lang',
address: 'Itterstr 3',
zip: '42719',
location: 'Solingen',
food_preferences: [],
activity: 'Teilnehmer*in',
gender: 'Weiblich',
email: 'philipp@aaa.de',
birthday: '1991-06-20',
agegroup: '',
group: '',
agegroup_leader: '',
emergency_phone: '',
phone: '+49 176 70342420',
misc: '',
foto: false,
vorteam: false,
},
meta: {
submitRequest: null,
errorFields: [],
active: 0,
slides: [
'Persönliches',
'Veranstaltung',
'Sonstiges'
],
activities: [
{"id": "Orga", "name": "Orga"},
{"id": "Teilnehmer*in", "name": "Teilnehmer*in"},
],
genders: [
{"id": "Männlich", "name": "Männlich"},
{"id": "Weiblich", "name": "Weiblich"},
{"id": "Divers", "name": "Divers"},
],
groups: [
{"id": "Gandalf", "name": "Gandalf"},
{"id": "Tenkterer", "name": "Tenkterer"},
],
agegroups: [
{"id": "Biber", "name": "Biber"},
{"id": "Wölfling", "name": "Wölfling"},
{"id": "Jungpfadfinder", "name": "Jungpfadfinder"},
{"id": "Pfadfinder", "name": "Pfadfinder"},
{"id": "Rover", "name": "Rover"},
{"id": "Leiter", "name": "Leiter"},
],
agegroups_leaders: [
{"id": "Biber", "name": "Biber"},
{"id": "Wölfling", "name": "Wölfling"},
{"id": "Jungpfadfinder", "name": "Jungpfadfinder"},
{"id": "Pfadfinder", "name": "Pfadfinder"},
{"id": "Rover", "name": "Rover"},
],
boolean: [
{"id": "Ja", "name": "Ja"},
{"id": "Nein", "name": "Nein"},
],
foodPreferences: [
{"id": "Fleisch", "name": "Fleisch"},
{"id": "Vegan", "name": "Vegan"},
{"id": "Glutenfrei", "name": "Glutenfrei"},
{"id": "Laktosefrei", "name": "Laktosefrei"},
]
},
methods: {
hasAddress() {
return this.data.location && this.data.address && this.data.zip;
},
slideTo(e, index) {
if (e !== null) {
e.preventDefault();
}
this.scrollForm(this.$refs.form);
this.meta.active = index;
this.$refs.slider.scrollLeft = this.$refs.slider.scrollWidth / this.meta.slides.length * index;
// this.$refs.mobileSlider.scrollLeft = this.$refs.mobileSlider.scrollWidth / this.meta.slides.length * index;
},
scrollForm(el) {
var margin = window.getComputedStyle(el).marginTop.replace('px', '');
scrollToElement(el, 300, (margin?margin:0) * -1);
},
onNextButtonClick(e) {
if (this.meta.active != this.meta.slides.length - 1) {
this.slideTo(e, this.meta.active + 1);
}
},
onPrevButtonClick(e) {
if (this.meta.active != 0) {
this.slideTo(e, this.meta.active - 1);
}
},
submit() {
var _self = this;
var promise = fetch(window.location.href, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-WINTER-REQUEST-HANDLER': this.meta.submitRequest,
'X-WINTER-REQUEST-PARTIALS': [],
'X-Requested-With': 'XMLHttpRequest',
},
body: JSON.stringify(this.data),
});
promise.then(function(response) {
if (response.status === 422) {
response.json().then((errors) => {
_self.scrollToFirstError(errors);
Object.keys(errors).forEach((field) => {
toasted.error(errors[field].join('<br>'));
});
});
}
});
},
scrollToFirstError(errors) {
if (Object.keys(errors).length === 0) {
return;
}
var firstField = Object.keys(errors)[0];
var field = this.$refs.form.querySelector('[name="'+firstField+'"]');
if (field === null) {
return;
}
var slideElement = field.closest('.slider-element');
this.slideTo(null, Array.from(slideElement.parentNode.children).indexOf(slideElement));
},
},
spreads: {
prevButton: {
[':class']() {
return this.active == 0 ? 'opacity-40' : '';
}
},
nextButton: {
[':class']() {
return this.active == this.meta.slides.length - 1 ? 'opacity-40' : '';
}
},
},
init() {
this.slideTo(null, this.meta.active);
}
};
}

30
assets/scrollToElement.js Normal file
View File

@ -0,0 +1,30 @@
/*
* y: the y coordinate to scroll, 0 = top
* duration: scroll duration in milliseconds; default is 0 (no transition)
* element: the html element that should be scrolled ; default is the main scrolling element
*/
function scrollToY (y, duration = 0, element = document.scrollingElement) {
// cancel if already on target position
if (element.scrollTop === y) return;
const cosParameter = (element.scrollTop - y) / 2;
let scrollCount = 0, oldTimestamp = null;
function step (newTimestamp) {
if (oldTimestamp !== null) {
// if duration is 0 scrollCount will be Infinity
scrollCount += Math.PI * (newTimestamp - oldTimestamp) / duration;
if (scrollCount >= Math.PI) return element.scrollTop = y;
element.scrollTop = cosParameter + y + cosParameter * Math.cos(scrollCount);
}
oldTimestamp = newTimestamp;
window.requestAnimationFrame(step);
}
window.requestAnimationFrame(step);
}
export default function(element, duration = 0, addedOffset = 0) {
const offset = Math.round(element.getBoundingClientRect().top);
scrollToY(document.scrollingElement.scrollTop + offset + addedOffset, duration);
}

55
components/EventForm.php Normal file
View File

@ -0,0 +1,55 @@
<?php
namespace Zoomyboy\Event\Components;
use Cms\Classes\ComponentBase;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Lang;
use Input;
use Winter\Storm\Support\Facades\Validator;
class EventForm extends ComponentBase
{
public function componentDetails()
{
return [
'name' => 'EventForm Component',
'description' => 'No description provided yet...',
];
}
public function defineProperties()
{
return [];
}
public function onSubmit(): JsonResponse
{
$validator = Validator::make(Input::all(), [
'activity' => 'required|max:255',
'gender' => 'required|max:255',
'firstname' => 'required|max:255',
'lastname' => 'required|max:255',
'birthday' => 'required|date|before_or_equal:'.now()->format('Y-m-d'),
'address' => 'required|max:255',
'zip' => 'required|numeric',
'location' => 'required|max:255',
'phone' => 'required|max:255',
'email' => 'required|max:255|email',
'agegroup' => 'required|max:255',
'group' => 'nullable|max:255',
'agegroup_leader' => 'nullable|max:255',
'emergency_phone' => 'required|max:255',
'food_preferences' => 'array',
'misc' => '',
'foto' => '',
], [], Lang::get('zoomyboy.event::validation.attributes'));
if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}
$fields = $validator->validated();
dd($fields);
}
}

View File

@ -0,0 +1,317 @@
{% set labelwidth = 'w-48' %}
{% set p=participant %}
{% set formContainerClass = "bg-white rounded-lg shadow-lg " %}
{% macro field(context, name, label, required, type) %}
<label class="w-full border border-solid border-gray-500 focus-within:border-primary rounded-lg relative flex">
<input name="{{name}}" type="{{type|default('text')}}" id="{{name}}" placeholder=" " class="bg-white rounded-lg focus:outline-none text-gray-600 text-left placeholder-white peer py-2 px-3 w-full" x-model="data.{{name}}" />
<span
class="transition-all duration-200 absolute text-gray-600 left-2 flex bg-white items-center -top-3 px-1 text-sm peer-placeholder-shown:bottom-0 peer-placeholder-shown:-top-0 peer-placeholder-shown:text-base peer-focus:-top-3 peer-focus:bottom-auto peer-focus:text-sm"
>{{label}} {% if required %} <span class="text-red-800 ml-1">*</span> {% endif %}</span
>
</label>
{% endmacro %}
{% macro textarea(context, name, label, required, type) %}
<label class="w-full border border-solid border-gray-500 focus-within:border-primary rounded-lg relative flex">
<textarea name="{{name}}" rows="6" id="{{name}}" class="bg-white rounded-lg focus:outline-none text-gray-600 text-left placeholder-white peer py-2 px-3 w-full" x-model="data.{{name}}">
</textarea>
<span
class="transition-all duration-200 absolute text-gray-600 left-2 flex bg-white items-center -top-3 px-1 text-sm"
>{{label}} {% if required %} <span class="text-red-800 ml-1">*</span> {% endif %}</span
>
</label>
{% endmacro %}
{% macro select(context, name, label, required, options) %}
<label class="w-full border border-solid border-gray-500 focus-within:border-primary rounded-lg relative flex" for="{{name}}">
<select name="{{name}}" id="{{name}}" class="bg-white rounded-lg focus:outline-none text-gray-600 text-left peer py-2 px-3 w-full" x-model="data.{{name}}">
<option :selected="data.{{name}} === ''" value="">-- kein --</option>
<template x-for="model in meta.{{options}}">
<option :selected="data.{{name}} === model.id" :value="model.id" x-text="model.name"></option>
</template>
</select>
<span
class="absolute text-gray-600 left-2 flex bg-white items-center -top-3 px-1 text-sm"
>{{label}} {% if required %} <span class="text-red-800 ml-1">*</span> {% endif %}</span
>
</label>
{% endmacro %}
{% macro radio(context, name, label, required, options) %}
<div>
<span
class="text-gray-600 text-sm"
>{{label}} {% if required %} <span class="text-red-800 ml-1">*</span> {% endif %}</span
>
<div class="flex flex-col space-y-1">
<template x-for="option, index in meta.{{options}}">
<label :for="`{{name}}-${index}`" class="block relative flex items-center">
<input type="radio" name="{{name}}" :value="option.name" x-model="data.{{name}}" class="peer absolute invisible" :id="`{{name}}-${index}`" />
<span class="border-neutral-400 border-4 border-solid peer-checked:border-primary absolute left-0 w-6 h-6 rounded-full block"></span>
<span class="peer-checked:bg-primary left-2 w-2 h-2 absolute rounded-full block"></span>
<span class="pl-8" x-text="option.name"></span>
</label>
</template>
</div>
</div>
{% endmacro %}
{% macro yesno(context, name, label, required) %}
<div>
<div class="flex flex-col space-y-1">
<label for="{{name}}" class="block relative flex items-center">
<input type="checkbox" name="{{name}}" x-model="data.{{name}}" class="peer absolute invisible" id="{{name}}" />
<span class="border-neutral-400 border-4 border-solid peer-checked:border-primary absolute left-0 w-6 h-6 rounded block"></span>
<span class="peer-checked:bg-primary left-2 w-2 h-2 absolute rounded-sm block"></span>
<span class="pl-8">{{label}}</span>
</label>
</div>
</div>
{% endmacro %}
{% macro checkboxes(context, name, label, required, options) %}
<div>
<span
class="text-gray-600 flex text-sm"
>{{label}} {% if required %} <span class="text-red-800 ml-1">*</span> {% endif %}</span
>
<div class="flex flex-col space-y-1">
<template x-for="option, index in meta.{{options}}">
<label :for="`{{name}}-${index}`" class="block relative flex items-center">
<input type="checkbox" name="{{name}}[]" :value="option.name" x-model="data.{{name}}" class="peer absolute invisible" :id="`{{name}}-${index}`" />
<span class="border-neutral-400 border-4 border-solid peer-checked:border-primary absolute left-0 w-6 h-6 rounded block"></span>
<span class="peer-checked:bg-primary left-2 w-2 h-2 absolute rounded-sm block"></span>
<span class="pl-8" x-text="option.name"></span>
</label>
</template>
</div>
</div>
{% endmacro %}
{% import _self as form %}
<div x-data="{
data: eventRegistration.data,
meta: {
...eventRegistration.meta,
submitRequest: '{{__SELF__.alias ~ '::onSubmit'}}',
},
...eventRegistration.methods,
...eventRegistration.spreads,
}" x-init="eventRegistration.init">
{#+++ registrationForm +++#}
<form x-ref="form" @submit.prevent="submit" class="my-6" novalidate>
<div x-ref="eventFormContainer" class="{{formContainerClass}}">
{% set arrowClass = "flex flex-col md:flex-row items-center justify-center md:justify-start px-2 flex-auto md:pl-6 h-12 md:h-16 transition duration-300" %}
{% set positionClass = "flex items-center justify-center w-4 md:w-6 h-4 md:h-6 rounded-full bg-teal-800 mr-2 text-teal-200 font-goudy transition duration-300" %}
{% set arrowFont = "text-sm text-teal-100" %}
<!--
<div class="sticky top-0 z-10 hidden sm:flex overflow-hidden rounded-t-lg">
<template x-for="(slide, index) in meta.slides">
<a @click="slideTo($event, index)" href="#" class="{{arrowClass}}" :class="{ 'bg-emerald-500': index <= meta.active, 'bg-primary-700': index > meta.active }">
<span class="{{positionClass}}" x-html="index+1"></span>
<span class="{{arrowFont}}" x-html="slide"></span>
</a>
</template>
</div>
-->
<!--
<div class="flex items-center px-6 overflow-auto overflow-hidden rounded-t-lg sm:hidden bg-primary-700">
<a @click="slideTo($event, meta.active-1)" href="#" class="{{positionClass}} w-6 h-6 flex-none" :class="{'opacity-40': meta.active == 0}">
<svg class="{{arrowFont}} fill-current w-3 h-3"><use xlink:href="{{'icons/sprite.svg'|media}}#chevron"></use></svg>
</a>
<div x-ref="mobileSlider" class="flex flex-no-wrap w-full overflow-auto snap transition-all" x-on:resize.debounce.window="$refs.mobileSlider.scrollLeft = $refs.mobileSlider.scrollWidth / meta.slides.length * meta.active">
<template x-for="(slide, index) in meta.slides">
<div class="flex-none w-full">
<div @click="slideTo($event, index)" href="#" class="{{arrowClass}}">
<span class="{{positionClass}}" x-html="index+1"></span>
<span class="{{arrowFont}}" x-html="slide"></span>
</div>
</div>
</template>
</div>
<a @click="slideTo($event, meta.active+1)" href="#" class="{{positionClass}} w-6 h-6 flex-none" :class="{'opacity-40': meta.active == meta.slides.length - 1}">
<svg class="{{arrowFont}} fill-current w-3 h-3 transform rotate-180"><use xlink:href="{{'icons/sprite.svg'|media}}#chevron"></use></svg>
</a>
</div>
-->
<div class="py-8">
<div x-ref="slider" class="relative flex flex-no-wrap overflow-hidden snap-x snap-mandatory snap-always scroll-smooth">
{% set labelClass = "fancy-textinput size-lg dark" %}
{% set gliderElClass = "w-full flex-none snap-center slider-element" %}
{% set descClass = "text-sm sm_text-base text-gray-800 leading-tight mb-5" %}
<div class="{{gliderElClass}}">
<div class="{{descClass}}">Gebe hier deine Funktion an, die du auf der Veranstaltung wahrnehmen willst. Danach benötigen wir ein paar persönliche Daten von dir. </div>
<div class="{{descClass}}"></div>
<div class="grid sm:grid-cols-2 gap-4 mt-6">
{{ form.select(_context, 'activity', 'Funktion', true, 'activities') }}
{{ form.select(_context, 'gender', 'Geschlecht', true, 'genders') }}
{{ form.field(_context, 'firstname', 'Vorname', true) }}
{{ form.field(_context, 'lastname', 'Nachname', true) }}
{{ form.field(_context, 'birthday', 'Geburtsdatum', true, 'date') }}
{{ form.field(_context, 'address', 'Straße & Hausnr', true) }}
{{ form.field(_context, 'zip', 'PLZ', true) }}
{{ form.field(_context, 'location', 'Ort', true) }}
{{ form.field(_context, 'phone', 'Telefonnummer', true, 'tel') }}
{{ form.field(_context, 'email', 'E-Mail-Adresse', true, 'email') }}
</div>
{% partial __SELF__.alias ~ '::navigation' %}
</div>
<div class="{{gliderElClass}}">
<div class="{{descClass}}">Gebe hier deine Gruppenzugehörigkeit und weitere wichtige Infos zur Veranstaltung an.</div>
<div class="grid sm:grid-cols-2 gap-4 mt-6">
{{ form.select(_context, 'group', 'Stamm', false, 'groups') }}
{{ form.field(_context, 'emergency_phone', 'Notfallkontakt (Tel)', true) }}
{{ form.select(_context, 'agegroup', 'Stufe (zum Zeitpunkt des Lagers)', true, 'agegroups') }}
<template x-if="data.agegroup === 'Leiter'">
{{ form.select(_context, 'agegroup_leader', 'Stufe die du leitest', false, 'agegroups_leaders') }}
</template>
<template x-if="data.agegroup !== 'Leiter'">
<div></div>
</template>
<template x-if="data.activity === 'Orga'">
{{ form.radio(_context, 'vorteam', 'Möchtest du am Vorteam teilnehmen?', true, 'boolean') }}
</template>
{{ form.checkboxes(_context, 'food_preferences', 'Essgewohnheiten & Unverträglichkeiten', false, 'foodPreferences') }}
</div>
{% partial __SELF__.alias ~ '::navigation' %}
</div>
<div class="{{gliderElClass}}">
<div class="{{descClass}}">Fast geschafft! Wenn du sonst noch etwas loswerden willst, kannst du das hier tun.</div>
<div class="flex flex-row items-center p-4 bg-red-100 border-2 border-red-400 rounded form-group shadow-sm" x-show="data.agegroup === 'Leiter'">
<div class="flex items-center justify-center w-8 h-8 mr-3 bg-red-500 rounded-full flex-none">
<div class="flex items-center justify-center w-5 h-5 bg-red-100 rounded-full">
<svg class="w-5 h-5 text-red-700 border-gray-400 fill-current border-5 fill-current"><use xlink:href="/themes/october-theme/resources/img/sprite.svg#exclamation"></use></svg>
</div>
</div>
<span class="grow text-sm font-semibold text-red-800">
<p>Als teilnehmende*r Leiter*in ist für die Teilnahme am BeLa ein eingereichtes Führungszeugnis ohne Einträge und eine aktive Präventionsschulung zwingend erforderlich.</p>
<p class="mt-2">Dies muss spätestens zu Beginn des BeLas vorliegen. Ansonsten kannst du nicht am BeLa teilnehmen.</p>
<p class="mt-2">Weitere Infos findest du in der <a class="text-rose-500 hover:text-rose-900" target="_BLANK" href="https://dpsg-koeln.de/fuer-mitglieder/faqs#pravention">FAQ-Sektion der Diözese Köln zu Prävention</a>. Außerdem kannst du dich auch an deinen StaVo oder den <a class="text-rose-500 hover:text-rose-900" href="mailto:vorstand@dpsgbergischland.de">Bezirksvorstand</a> wenden.</p>
</span>
</div>
<div class="grid gap-4 mt-6">
{{ form.textarea(_context, 'misc', 'Sonstige Anmerkungen', false) }}
{{ form.yesno(_context, 'foto', 'Ich akzeptiere, dass vor Ort Foto-und Videoaufnahmen von mir gemacht werden dürfen.', false) }}
</div>
{% partial __SELF__.alias ~ '::navigation' %}
</div>
</div>
</div>
</div>
<!--
{#+++ Sidebar +++#}
<div class="relative hidden event-form-sidebar lg_block">
<div class="absolute w-full h-full">
<div class="sticky h-full bg-white rounded-lg shadow top-10">
<div class="relative">
<img src="{{__SELF__.event.header_image | media}}" class="object-cover w-full h-48">
<div class="absolute bottom-0 w-full px-4 py-2 leading-tight bg-white-t600">
<h3 class="text-primary-800 font-goudy">{{__SELF__.event.name}}</h3>
<div class="text-xs">{{ __SELF__.event.dates }} {{ __SELF__.event.times }}</div>
</div>
</div>
<div class="overflow-auto" style="height: calc(100% - 12rem);">
<div class="p-4 text-gray-800 grid row-gap-2">
<h3 class="leading-tight text-primary-800 font-goudy">Persönliche Daten</h3>
<div class="leading-tight">
<div class="text-xs text-gray-600">Funktion</div>
<div class="text-sm" x-html="data.activity"></div>
</div>
<div class="leading-tight" x-show="hasAddress()">
<div class="text-xs text-gray-600">Name & Adresse</div>
<div class="text-sm">
<div x-html="`${data.firstname} ${data.lastname}`"></div>
<div x-html="data.address"></div>
<div x-html="`${data.zip} ${data.location}`"></div>
</div>
</div>
<div class="leading-tight" x-show="data.birthday">
<div class="text-xs text-gray-600">Geburtsdatum</div>
<div class="text-sm" x-html="data.birthday"></div>
</div>
<div class="leading-tight" x-show="data.phone || data.email">
<div class="text-xs text-gray-600">Kontakt</div>
<div class="text-sm">
<div x-show="data.phone" x-html="`Tel: ${data.phone}`"></div>
<div x-show="data.email" x-html="`E-Mail: ${data.email}`"></div>
</div>
</div>
<h3 class="mt-4 leading-tight text-primary-800 font-goudy" x-show="data.region_id !=-1 || data.group_id != -1">Gruppierung</h3>
<div class="leading-tight" x-show="data.region_id != -1 || data.group_id != -1">
<div class="text-xs text-gray-600">Gruppierung</div>
<div class="text-sm">
<div x-html="data.group"></div>
</div>
</div>
<h3 class="mt-4 leading-tight text-primary-800 font-goudy" x-show="data.food_preferences.length > 0">Verpflegung</h3>
<div class="leading-tight" x-show="data.food_preferences.length > 0">
<div class="text-xs text-gray-600">Lebensmittelunverträglichkeiten</div>
<ul>
<template x-for="fi in data.food_preferences">
<li class="text-sm" x-html="fi"></li>
</template>
<li class="text-sm" x-show="data.further_food_intolerances" x-html="data.further_food_intolerances"></li>
</ul>
</div>
<h3 class="mt-4 leading-tight text-primary-800 font-goudy" x-show="data.rooms || data.add_misc !== null || data.misc">Sonstiges</h3>
<div class="leading-tight" x-show="data.add_misc !== null">
<div class="text-xs text-gray-600">Barrierefreiheit</div>
<div class="text-sm" x-html="data.add_misc"></div>
</div>
<div class="leading-tight" x-show="data.rooms">
<div class="text-xs text-gray-600">Zimmerwünsche</div>
<div class="text-sm" x-html="data.rooms"></div>
</div>
<div class="leading-tight" x-show="data.misc">
<div class="text-xs text-gray-600">Sonstige Wünsche</div>
<div class="text-sm" x-html="data.misc"></div>
</div>
</div>
</div>
</div>
</div>
</div>
-->
</form>
<!--
{#+++ Pflichtfeld +++ #}
<div class="mt-2 text-lg text-gray-800">
<span class="text-red-800">*</span> Pflichtfeld
</div>
{#+++ popup +++#}
<div class="popup" id="waiting-popup">
<div class="relative text-gray-600 popup-inner">
<script class="tmpl" type="text/html">
<div class="absolute top-0 right-0 flex items-center justify-center w-16 h-16 mt-4 mr-4 bg-yellow-500 rounded-full opacity-50">
<div class="flex items-center justify-center w-10 h-10 bg-yellow-100 rounded-full">
<svg class="w-10 h-10 text-yellow-700 border-gray-400 fill-current border-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M2.93 17.07A10 10 0 1 1 17.07 2.93 10 10 0 0 1 2.93 17.07zm12.73-1.41A8 8 0 1 0 4.34 4.34a8 8 0 0 0 11.32 11.32zM9 5h2v6H9V5zm0 8h2v2H9v-2z"/></svg>
</div>
</div>
<h2 class="flex-shrink-0 text-xl font-semibold text-gray-800">Auf die Warteliste setzen?</h2>
<h2 class="flex-shrink-0 text-sm font-semibold text-gray-600">Für <%= eventname %></h2>
<div class="relative mt-5 richeditor content small">
<h2>Was ist die Warteliste?</h2>
Wenn du dich für die Warteliste einträgst, wirst du automatisch per E-Mail benachrichtigt, wenn bei dieser Unterveranstaltung ein Platz frei wird. Du hast dann die Möglichkeit, zu dieser Unterveranstaltung zu wechseln, und meldest dich zeitgleich von einer beliebigen anderen Unterveranstaltung ab.
<h2>Was nun</h2>
Wähle bitte neben dieser Unterveranstaltung deine "regulären" Veranstatungen aus.
</div>
<div class="flex justify-end mt-6">
<button class="mr-6 btn btn-shine btn-primary btn-sm" type="button" data-confirm>Auf Warteliste setzen</button>
<button class="btn-shine btn btn-danger btn-sm" type="button" data-reject>Nee, doch nicht</button>
</div>
</script>
<div></div>
</div>
</div>
-->
</div>

View File

@ -0,0 +1,9 @@
<div class="flex mt-10 justify-evenly">
<a href="#" class="bg-primary hover:bg-emerald-600 px-4 py-2 shadow text-teal-200 leading-none rounded-lg" @click.prevent="onPrevButtonClick" :class="{'invisible': meta.active === 0}">
Zurück
</a>
<a href="#" class="bg-primary hover:bg-emerald-600 px-4 py-2 shadow text-teal-200 leading-none rounded-lg" @click.prevent="onNextButtonClick" x-show="meta.active != meta.slides.length-1">
Weiter
</a>
<button type="submit" class="bg-primary hover:bg-emerald-600 px-4 py-2 shadow text-teal-200 leading-none rounded-lg" x-show="meta.active == meta.slides.length-1">{% if p %} Anmeldung aktualisieren {% else %} Anmeldung absenden {% endif %}</button>
</div>

View File

@ -0,0 +1,35 @@
<?php namespace Zoomyboy\Event\Controllers;
use BackendMenu;
use Backend\Classes\Controller;
/**
* Participant Back-end Controller
*/
class Participant extends Controller
{
/**
* @var array Behaviors that are implemented by this controller.
*/
public $implement = [
'Backend.Behaviors.FormController',
'Backend.Behaviors.ListController'
];
/**
* @var string Configuration file for the `FormController` behavior.
*/
public $formConfig = 'config_form.yaml';
/**
* @var string Configuration file for the `ListController` behavior.
*/
public $listConfig = 'config_list.yaml';
public function __construct()
{
parent::__construct();
BackendMenu::setContext('Zoomyboy.Event', 'event', 'participant');
}
}

View File

@ -0,0 +1,21 @@
<div data-control="toolbar">
<a
href="<?= Backend::url('zoomyboy/event/participant/create') ?>"
class="btn btn-primary oc-icon-plus">
New Participant
</a>
<button
class="btn btn-danger oc-icon-trash-o"
disabled="disabled"
onclick="$(this).data('request-data', { checked: $('.control-list').listWidget('getChecked') })"
data-request="onDelete"
data-request-confirm="Are you sure you want to delete the selected Participants?"
data-trigger-action="enable"
data-trigger=".control-list input[type=checkbox]"
data-trigger-condition="checked"
data-request-success="$(this).prop('disabled', 'disabled')"
data-stripe-load-indicator>
Delete selected
</button>
</div>

View File

@ -0,0 +1,31 @@
# ===================================
# Form Behavior Config
# ===================================
# Record name
name: Participant
# Model Form Field configuration
form: $/zoomyboy/event/models/participant/fields.yaml
# Model Class name
modelClass: Zoomyboy\Event\Models\Participant
# Default redirect location
defaultRedirect: zoomyboy/event/participant
# Create page
create:
title: backend::lang.form.create_title
redirect: zoomyboy/event/participant/update/:id
redirectClose: zoomyboy/event/participant
# Update page
update:
title: backend::lang.form.update_title
redirect: zoomyboy/event/participant
redirectClose: zoomyboy/event/participant
# Preview page
preview:
title: backend::lang.form.preview_title

View File

@ -0,0 +1,50 @@
# ===================================
# List Behavior Config
# ===================================
# Model List Column configuration
list: $/zoomyboy/event/models/participant/columns.yaml
# Model Class name
modelClass: Zoomyboy\Event\Models\Participant
# List Title
title: Manage Participants
# Link URL for each record
recordUrl: zoomyboy/event/participant/update/:id
# Message to display if the list is empty
noRecordsMessage: backend::lang.list.no_records
# Records to display per page
recordsPerPage: 20
# Options to provide the user when selecting how many records to display per page
perPageOptions: [20, 40, 80, 100, 120]
# Display page numbers with pagination, disable to improve performance
showPageNumbers: true
# Displays the list column set up button
showSetup: true
# Displays the sorting link on each column
showSorting: true
# Default sorting column
# defaultSort:
# column: created_at
# direction: desc
# Display checkboxes next to each record
showCheckboxes: true
# Toolbar widget configuration
toolbar:
# Partial for toolbar buttons
buttons: list_toolbar
# Search widget configuration
search:
prompt: backend::lang.list.search_prompt

View File

@ -0,0 +1,48 @@
<?php Block::put('breadcrumb') ?>
<ul>
<li><a href="<?= Backend::url('zoomyboy/event/participant') ?>">Participant</a></li>
<li><?= e($this->pageTitle) ?></li>
</ul>
<?php Block::endPut() ?>
<?php if (!$this->fatalError): ?>
<?= Form::open(['class' => 'layout']) ?>
<div class="layout-row">
<?= $this->formRender() ?>
</div>
<div class="form-buttons">
<div class="loading-indicator-container">
<button
type="submit"
data-request="onSave"
data-hotkey="ctrl+s, cmd+s"
data-load-indicator="Creating Participant..."
class="btn btn-primary">
Create
</button>
<button
type="button"
data-request="onSave"
data-request-data="close:1"
data-hotkey="ctrl+enter, cmd+enter"
data-load-indicator="Creating Participant..."
class="btn btn-default">
Create and Close
</button>
<span class="btn-text">
or <a href="<?= Backend::url('zoomyboy/event/participant') ?>">Cancel</a>
</span>
</div>
</div>
<?= Form::close() ?>
<?php else: ?>
<p class="flash-message static error"><?= e($this->fatalError) ?></p>
<p><a href="<?= Backend::url('zoomyboy/event/participant') ?>" class="btn btn-default">Return to participant list</a></p>
<?php endif ?>

View File

@ -0,0 +1,2 @@
<?= $this->listRender() ?>

View File

@ -0,0 +1,19 @@
<?php Block::put('breadcrumb') ?>
<ul>
<li><a href="<?= Backend::url('zoomyboy/event/participant') ?>">Participant</a></li>
<li><?= e($this->pageTitle) ?></li>
</ul>
<?php Block::endPut() ?>
<?php if (!$this->fatalError): ?>
<div class="form-preview">
<?= $this->formRenderPreview() ?>
</div>
<?php else: ?>
<p class="flash-message static error"><?= e($this->fatalError) ?></p>
<p><a href="<?= Backend::url('zoomyboy/event/participant') ?>" class="btn btn-default">Return to participant list</a></p>
<?php endif ?>

View File

@ -0,0 +1,56 @@
<?php Block::put('breadcrumb') ?>
<ul>
<li><a href="<?= Backend::url('zoomyboy/event/participant') ?>">Participant</a></li>
<li><?= e($this->pageTitle) ?></li>
</ul>
<?php Block::endPut() ?>
<?php if (!$this->fatalError): ?>
<?= Form::open(['class' => 'layout']) ?>
<div class="layout-row">
<?= $this->formRender() ?>
</div>
<div class="form-buttons">
<div class="loading-indicator-container">
<button
type="submit"
data-request="onSave"
data-request-data="redirect:0"
data-hotkey="ctrl+s, cmd+s"
data-load-indicator="Saving Participant..."
class="btn btn-primary">
<u>S</u>ave
</button>
<button
type="button"
data-request="onSave"
data-request-data="close:1"
data-hotkey="ctrl+enter, cmd+enter"
data-load-indicator="Saving Participant..."
class="btn btn-default">
Save and Close
</button>
<button
type="button"
class="oc-icon-trash-o btn-icon danger pull-right"
data-request="onDelete"
data-load-indicator="Deleting Participant..."
data-request-confirm="Delete this participant?">
</button>
<span class="btn-text">
or <a href="<?= Backend::url('zoomyboy/event/participant') ?>">Cancel</a>
</span>
</div>
</div>
<?= Form::close() ?>
<?php else: ?>
<p class="flash-message static error"><?= e($this->fatalError) ?></p>
<p><a href="<?= Backend::url('zoomyboy/event/participant') ?>" class="btn btn-default">Return to participant list</a></p>
<?php endif ?>

18
lang/en/validation.php Normal file
View File

@ -0,0 +1,18 @@
<?php
return [
'attributes' => [
'firstname' => 'Vorname',
'lastname' => 'Nachname',
'activity' => 'Funktion',
'gender' => 'Geschlecht',
'birthday' => 'Geburtsdatum',
'address' => 'Straße & Hausnr',
'zip' => 'PLZ',
'location' => 'Ort',
'phone' => 'Telefonnummer',
'email' => 'E-Mail-Adresse',
'agegroup' => 'Stufe',
'emergency_phone' => 'Notfallkontakt',
],
];

76
models/Participant.php Normal file
View File

@ -0,0 +1,76 @@
<?php
namespace Zoomyboy\Event\Models;
use Model;
/**
* Participant Model.
*/
class Participant extends Model
{
use \Winter\Storm\Database\Traits\Validation;
/**
* @var string the database table used by the model
*/
public $table = 'zoomyboy_event_participants';
/**
* @var array Guarded fields
*/
protected $guarded = [];
/**
* @var array Fillable fields
*/
protected $fillable = [];
/**
* @var array Validation rules for attributes
*/
public $rules = [];
/**
* @var array Attributes to be cast to native types
*/
protected $casts = [];
/**
* @var array Attributes to be cast to JSON
*/
protected $jsonable = [];
/**
* @var array Attributes to be appended to the API representation of the model (ex. toArray())
*/
protected $appends = [];
/**
* @var array Attributes to be removed from the API representation of the model (ex. toArray())
*/
protected $hidden = [];
/**
* @var array Attributes to be cast to Argon (Carbon) instances
*/
protected $dates = [
'created_at',
'updated_at',
];
/**
* @var array Relations
*/
public $hasOne = [];
public $hasMany = [];
public $hasOneThrough = [];
public $hasManyThrough = [];
public $belongsTo = [];
public $belongsToMany = [];
public $morphTo = [];
public $morphOne = [];
public $morphMany = [];
public $attachOne = [];
public $attachMany = [];
}

View File

@ -0,0 +1,20 @@
# ===================================
# List Column Definitions
# ===================================
columns:
firstname:
label: Vorname
lastname:
label: Nachname
email:
label: E-Mail-Adresse
group:
label: Stamm
activity:
label: Funktion

View File

@ -0,0 +1,8 @@
# ===================================
# Form Field Definitions
# ===================================
fields:
id:
label: ID
disabled: true

View File

@ -0,0 +1,43 @@
<?php
namespace Zoomyboy\Event\Updates;
use Schema;
use Winter\Storm\Database\Schema\Blueprint;
use Winter\Storm\Database\Updates\Migration;
class CreateParticipantsTable extends Migration
{
public function up()
{
Schema::create('zoomyboy_event_participants', function (Blueprint $table) {
$table->engine = 'InnoDB';
$table->increments('id');
$table->string('gender');
$table->string('firstname');
$table->string('lastname');
$table->date('birthday');
$table->string('email');
$table->string('group')->nullable();
$table->string('agegroup');
$table->string('agegroup_leader');
$table->boolean('vorteam');
$table->boolean('foto');
$table->string('emergency_phone');
$table->string('address');
$table->string('zip');
$table->string('location');
$table->string('phone');
$table->string('activity');
$table->json('food_preferences');
$table->string('further_food_preferences')->nullable();
$table->text('misc')->nullable();
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('zoomyboy_event_participants');
}
}

3
updates/version.yaml Normal file
View File

@ -0,0 +1,3 @@
1.0.1: First version of event
1.0.2:
- create_participants_table.php