From 5eeda308ce61da1662383fefa2700c16d9418227 Mon Sep 17 00:00:00 2001 From: philipp lang Date: Sat, 7 Jan 2023 16:10:18 +0100 Subject: [PATCH] Initial commit --- Plugin.php | 92 +++++++ assets/eventregistration.js | 168 ++++++++++++ assets/scrollToElement.js | 30 ++ components/EventForm.php | 55 ++++ components/eventform/default.htm | 317 ++++++++++++++++++++++ components/eventform/navigation.htm | 9 + controllers/Participant.php | 35 +++ controllers/participant/_list_toolbar.htm | 21 ++ controllers/participant/config_form.yaml | 31 +++ controllers/participant/config_list.yaml | 50 ++++ controllers/participant/create.htm | 48 ++++ controllers/participant/index.htm | 2 + controllers/participant/preview.htm | 19 ++ controllers/participant/update.htm | 56 ++++ lang/en/validation.php | 18 ++ models/Participant.php | 76 ++++++ models/participant/columns.yaml | 20 ++ models/participant/fields.yaml | 8 + updates/create_participants_table.php | 43 +++ updates/version.yaml | 3 + 20 files changed, 1101 insertions(+) create mode 100644 Plugin.php create mode 100644 assets/eventregistration.js create mode 100644 assets/scrollToElement.js create mode 100644 components/EventForm.php create mode 100644 components/eventform/default.htm create mode 100644 components/eventform/navigation.htm create mode 100644 controllers/Participant.php create mode 100644 controllers/participant/_list_toolbar.htm create mode 100644 controllers/participant/config_form.yaml create mode 100644 controllers/participant/config_list.yaml create mode 100644 controllers/participant/create.htm create mode 100644 controllers/participant/index.htm create mode 100644 controllers/participant/preview.htm create mode 100644 controllers/participant/update.htm create mode 100644 lang/en/validation.php create mode 100644 models/Participant.php create mode 100644 models/participant/columns.yaml create mode 100644 models/participant/fields.yaml create mode 100644 updates/create_participants_table.php create mode 100644 updates/version.yaml diff --git a/Plugin.php b/Plugin.php new file mode 100644 index 0000000..fe93a04 --- /dev/null +++ b/Plugin.php @@ -0,0 +1,92 @@ + '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, + ], + ]; + } +} diff --git a/assets/eventregistration.js b/assets/eventregistration.js new file mode 100644 index 0000000..af07bcf --- /dev/null +++ b/assets/eventregistration.js @@ -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('
')); + }); + }); + } + }); + }, + 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); + } + }; +} + diff --git a/assets/scrollToElement.js b/assets/scrollToElement.js new file mode 100644 index 0000000..b043803 --- /dev/null +++ b/assets/scrollToElement.js @@ -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); +} + diff --git a/components/EventForm.php b/components/EventForm.php new file mode 100644 index 0000000..3137e18 --- /dev/null +++ b/components/EventForm.php @@ -0,0 +1,55 @@ + '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); + } +} diff --git a/components/eventform/default.htm b/components/eventform/default.htm new file mode 100644 index 0000000..6ffea76 --- /dev/null +++ b/components/eventform/default.htm @@ -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) %} + +{% endmacro %} +{% macro textarea(context, name, label, required, type) %} + +{% endmacro %} +{% macro select(context, name, label, required, options) %} + +{% endmacro %} +{% macro radio(context, name, label, required, options) %} +
+ {{label}} {% if required %} * {% endif %} +
+ +
+
+{% endmacro %} +{% macro yesno(context, name, label, required) %} +
+
+ +
+
+{% endmacro %} +{% macro checkboxes(context, name, label, required, options) %} +
+ {{label}} {% if required %} * {% endif %} +
+ +
+
+{% endmacro %} +{% import _self as form %} + +
+ + {#+++ registrationForm +++#} +
+
+ {% 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" %} + + + + + +
+
+ {% 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" %} + +
+
Gebe hier deine Funktion an, die du auf der Veranstaltung wahrnehmen willst. Danach benötigen wir ein paar persönliche Daten von dir.
+
+
+ {{ 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') }} +
+ {% partial __SELF__.alias ~ '::navigation' %} +
+ +
+
Gebe hier deine Gruppenzugehörigkeit und weitere wichtige Infos zur Veranstaltung an.
+
+ {{ 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') }} + + + + {{ form.checkboxes(_context, 'food_preferences', 'Essgewohnheiten & Unverträglichkeiten', false, 'foodPreferences') }} +
+ {% partial __SELF__.alias ~ '::navigation' %} +
+ +
+
Fast geschafft! Wenn du sonst noch etwas loswerden willst, kannst du das hier tun.
+
+
+
+ +
+
+ +

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.

+

Dies muss spätestens zu Beginn des BeLas vorliegen. Ansonsten kannst du nicht am BeLa teilnehmen.

+

Weitere Infos findest du in der FAQ-Sektion der Diözese Köln zu Prävention. Außerdem kannst du dich auch an deinen StaVo oder den Bezirksvorstand wenden.

+
+
+
+ {{ 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) }} +
+ {% partial __SELF__.alias ~ '::navigation' %} +
+
+
+
+ + + + +
+ + +
diff --git a/components/eventform/navigation.htm b/components/eventform/navigation.htm new file mode 100644 index 0000000..2d0134a --- /dev/null +++ b/components/eventform/navigation.htm @@ -0,0 +1,9 @@ +
+ + Zurück + + + Weiter + + +
diff --git a/controllers/Participant.php b/controllers/Participant.php new file mode 100644 index 0000000..23958ae --- /dev/null +++ b/controllers/Participant.php @@ -0,0 +1,35 @@ + + + New Participant + + + + diff --git a/controllers/participant/config_form.yaml b/controllers/participant/config_form.yaml new file mode 100644 index 0000000..e3424ed --- /dev/null +++ b/controllers/participant/config_form.yaml @@ -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 diff --git a/controllers/participant/config_list.yaml b/controllers/participant/config_list.yaml new file mode 100644 index 0000000..3891df7 --- /dev/null +++ b/controllers/participant/config_list.yaml @@ -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 diff --git a/controllers/participant/create.htm b/controllers/participant/create.htm new file mode 100644 index 0000000..4199ed8 --- /dev/null +++ b/controllers/participant/create.htm @@ -0,0 +1,48 @@ + + + + +fatalError): ?> + + 'layout']) ?> + +
+ formRender() ?> +
+ +
+
+ + + + or Cancel + +
+
+ + + + + +

fatalError) ?>

+

Return to participant list

+ + diff --git a/controllers/participant/index.htm b/controllers/participant/index.htm new file mode 100644 index 0000000..766877d --- /dev/null +++ b/controllers/participant/index.htm @@ -0,0 +1,2 @@ + +listRender() ?> diff --git a/controllers/participant/preview.htm b/controllers/participant/preview.htm new file mode 100644 index 0000000..c8d6581 --- /dev/null +++ b/controllers/participant/preview.htm @@ -0,0 +1,19 @@ + + + + +fatalError): ?> + +
+ formRenderPreview() ?> +
+ + + +

fatalError) ?>

+

Return to participant list

+ + diff --git a/controllers/participant/update.htm b/controllers/participant/update.htm new file mode 100644 index 0000000..f0eba16 --- /dev/null +++ b/controllers/participant/update.htm @@ -0,0 +1,56 @@ + + + + +fatalError): ?> + + 'layout']) ?> + +
+ formRender() ?> +
+ +
+
+ + + + + or Cancel + +
+
+ + + + + +

fatalError) ?>

+

Return to participant list

+ + diff --git a/lang/en/validation.php b/lang/en/validation.php new file mode 100644 index 0000000..298399f --- /dev/null +++ b/lang/en/validation.php @@ -0,0 +1,18 @@ + [ + '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', + ], +]; diff --git a/models/Participant.php b/models/Participant.php new file mode 100644 index 0000000..cf75b49 --- /dev/null +++ b/models/Participant.php @@ -0,0 +1,76 @@ +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'); + } +} diff --git a/updates/version.yaml b/updates/version.yaml new file mode 100644 index 0000000..904ee01 --- /dev/null +++ b/updates/version.yaml @@ -0,0 +1,3 @@ +1.0.1: First version of event +1.0.2: + - create_participants_table.php