Compare commits

..

No commits in common. "d9586511c3f0f5f107e2c47a2c7789f1f2abb37c" and "6222871cb65dfd308a9f115a2be917767a2b36a9" have entirely different histories.

18 changed files with 243 additions and 513 deletions

View File

@ -90,14 +90,16 @@
<slide v-for="(section, index) in v.sections" :key="index"> <slide v-for="(section, index) in v.sections" :key="index">
<div v-if="active === index" class="w-full flex-none px-3 @xs:px-6"> <div v-if="active === index" class="w-full flex-none px-3 @xs:px-6">
<div class="text-sm sm_text-base text-gray-800 leading-tight mb-5" v-text="section.intro"></div> <div class="text-sm sm_text-base text-gray-800 leading-tight mb-5" v-text="section.intro"></div>
<div class="mt-6" :class="containerClasses"> <div class="grid grid-cols-2 @sm:grid-cols-4 @lg:grid-cols-6 gap-6 mt-6 items-start">
<div <div
v-for="(field, findex) in section.fields" v-for="(field, findex) in section.fields"
:key="findex" :key="findex"
class="flex justify-stretch relative group" class="flex justify-stretch relative group"
:class="{ :class="{
'hover:ring-edit hover:ring-4': editable, 'hover:ring-edit hover:ring-4': editable,
...colClassesForField(field), [colClasses.mobile[field.columns.mobile]]: true,
[colClasses.tablet[field.columns.tablet]]: true,
[colClasses.desktop[field.columns.desktop]]: true,
}" }"
> >
<component :is="resolveComponentName(field)" v-model="payload[field.key]" :fields="allFields" :payload="payload" :field="field" class="grow"></component> <component :is="resolveComponentName(field)" v-model="payload[field.key]" :fields="allFields" :payload="payload" :field="field" class="grow"></component>
@ -133,7 +135,19 @@
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" /> <path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" />
</svg> </svg>
</div> </div>
<div class="isolate self-center justify-self-end row-span-2 sm:pt-8 sm:pb-8 relative -right-3"></div> <div class="isolate self-center justify-self-end row-span-2 sm:pt-8 sm:pb-8 relative -right-3">
<!--
<div class="absolute right-0 top-0 h-full flex items-center justify-end">
<div class="bg-primary w-96 h-96 flex [clip-path:circle(50%_at_110%_50%)] sm:[clip-path:circle(50%_at_80%_50%)]"></div>
</div>
<div class="overflow-hidden rounded-l-lg rotate-2 shadow relative z-10 -left-2">
<img class="w-64 h-32 object-cover" src="" />
</div>
<div class="overflow-hidden rounded-l-lg -rotate-2 shadow relative left-2">
<img class="w-64 h-32 object-cover" src="" />
</div>
-->
</div>
<div class="self-end flex-grow pb-8 pl-8 pr-8 sm:pr-0 pt-6 sm:pt-0"> <div class="self-end flex-grow pb-8 pl-8 pr-8 sm:pr-0 pt-6 sm:pt-0">
<div class="font-bold text-xl">Vielen Dank für deine Anmeldung.</div> <div class="font-bold text-xl">Vielen Dank für deine Anmeldung.</div>
</div> </div>
@ -154,16 +168,41 @@ import DeleteIcon from './components/icons/DeleteIcon.vue';
import useToastify from './composables/useToastify.js'; import useToastify from './composables/useToastify.js';
import useFields from './composables/useFields.js'; import useFields from './composables/useFields.js';
import SettingIcon from './components/icons/SettingIcon.vue'; import SettingIcon from './components/icons/SettingIcon.vue';
import useColumns from './composables/useColumns.js';
const {scroll} = useScroll(); const {scroll} = useScroll();
const {errorFromResponse} = useToastify(); const {errorFromResponse} = useToastify();
const {colClassesForField, containerClasses} = useColumns();
const finished = ref(false); const finished = ref(false);
const eventForm = ref(null); const eventForm = ref(null);
const emits = defineEmits(['addSection', 'editSection', 'deleteSection', 'editField', 'deleteField', 'active']); const emits = defineEmits(['addSection', 'editSection', 'deleteSection', 'editField', 'deleteField', 'active']);
const colClasses = {
mobile: {
1: 'col-span-1',
2: 'col-span-2',
3: 'col-span-3',
4: 'col-span-4',
5: 'col-span-5',
6: 'col-span-6',
},
tablet: {
1: '@sm:col-span-1',
2: '@sm:col-span-2',
3: '@sm:col-span-3',
4: '@sm:col-span-4',
5: '@sm:col-span-5',
6: '@sm:col-span-6',
},
desktop: {
1: '@lg:col-span-1',
2: '@lg:col-span-2',
3: '@lg:col-span-3',
4: '@lg:col-span-4',
5: '@lg:col-span-5',
6: '@lg:col-span-6',
},
};
const props = defineProps({ const props = defineProps({
editable: { editable: {
type: Boolean, type: Boolean,

View File

@ -1,40 +0,0 @@
<template>
<box size="none" class="opacity-[0.8] hover:opacity-[1.0]" :type="type">
<div href="#" @click.prevent="emit('update:model-value', modelValue === index ? -1 : index)" class="flex items-center justify-between p-2">
<div v-text="title"></div>
<div class="flex items-center space-x-2">
<slot name="buttons"></slot>
<chevron :class="{'rotate-180': modelValue === index}" class="w-3 h-3"></chevron>
</div>
</div>
<div v-if="modelValue === index" class="p-2 pt-0">
<slot />
</div>
</box>
</template>
<script setup>
import Box from './Box.vue';
import Chevron from './icons/Chevron.vue';
const emit = defineEmits(['update:model-value']);
const props = defineProps({
type: {
required: true,
type: String,
},
title: {
required: true,
type: String,
},
modelValue: {
required: true,
type: Number,
},
index: {
required: true,
type: Number,
},
});
</script>

View File

@ -1,66 +0,0 @@
<template>
<div class="text-sm relative">
<div
class="flex items-center justify-center w-6 h-6 rounded-full border-solid border-2 absolute -top-2 -left-2 font-arvo font-bold"
:class="[colors[type].container, colors[type].lightText]"
v-text="step"
v-if="step"
></div>
<div class="flex flex-row items-center rounded form-group shadow-sm group" :class="[colors[type].container, type, 'box', sizes[size].container]">
<div class="flex-grow text-sm" :class="colors[type].text">
<slot />
</div>
</div>
</div>
</template>
<script setup>
import {ref} from 'vue';
const colors = ref({
success: {
container: 'bg-green-200 border-green-600',
lightText: 'text-green-700',
text: 'text-green-900',
},
info: {
container: 'bg-blue-200 border-blue-600',
lightText: 'text-blue-700',
text: 'text-blue-900',
},
default: {
container: 'bg-neutral-200 border-neutral-600',
lightText: 'text-neutral-700',
text: 'text-neutral-900',
},
});
const sizes = ref({
default: {
container: 'p-4 border-2',
},
sm: {
container: 'p-2 border',
},
none: {
container: 'border',
},
});
const props = defineProps({
type: {
required: true,
type: String,
},
size: {
required: false,
type: String,
default: () => 'default',
},
step: {
required: false,
type: Number,
default: () => 0,
},
});
</script>

View File

@ -1,5 +1,5 @@
<template> <template>
<span class="text-gray-600 flex bg-white items-center text-xs @sm:text-sm group-[.info]:bg-blue-200" :class="{'left-0 ': inline, 'left-2 px-1 absolute -top-3': !inline}"> <span class="text-gray-600 flex bg-white items-center text-xs @sm:text-sm" :class="{'left-0 ': inline, 'left-2 px-1 absolute -top-3': !inline}">
<span v-text="name"></span> <span v-show="required" class="text-red-800 ml-1">*</span> <span v-text="name"></span> <span v-show="required" class="text-red-800 ml-1">*</span>
<hint :value="hint" class="ml-2" v-if="hint" small></hint> <hint :value="hint" class="ml-2" v-if="hint" small></hint>
<button <button

View File

@ -1,14 +1,40 @@
<template> <template>
<box :step="step" :type="type"> <div class="text-sm relative">
<slot name="current" v-if="modelValue === step"></slot> <div
<slot name="finished" v-if="modelValue > step"></slot> class="flex items-center justify-center w-6 h-6 rounded-full border-solid border-2 absolute -top-2 -left-2 font-arvo font-bold"
<slot name="due" v-if="modelValue < step"></slot> :class="[colors[type].container, colors[type].lightText]"
</box> v-text="step"
></div>
<div class="flex flex-row items-center p-4 border-2 rounded form-group shadow-sm group" :class="[colors[type].container, type]">
<div class="flex-grow text-sm" :class="colors[type].text">
<slot name="current" v-if="modelValue === step"></slot>
<slot name="finished" v-if="modelValue > step"></slot>
<slot name="due" v-if="modelValue < step"></slot>
</div>
</div>
</div>
</template> </template>
<script setup> <script setup>
import {computed} from 'vue'; import {ref, computed} from 'vue';
import Box from './Box.vue';
const colors = ref({
success: {
container: 'bg-green-200 border-green-600',
lightText: 'text-green-700',
text: 'text-green-900',
},
info: {
container: 'bg-blue-200 border-blue-600',
lightText: 'text-blue-700',
text: 'text-blue-900',
},
default: {
container: 'bg-neutral-200 border-neutral-600',
lightText: 'text-neutral-700',
text: 'text-neutral-900',
},
});
const type = computed(() => { const type = computed(() => {
if (props.modelValue === props.step) { if (props.modelValue === props.step) {
@ -26,9 +52,8 @@ const props = defineProps({
type: Number, type: Number,
}, },
step: { step: {
required: false, required: true,
type: Number, type: Number,
default: () => 0,
}, },
}); });
</script> </script>

View File

@ -1,10 +1,21 @@
<template> <template>
<v-checkboxes :intro="field.intro" :options="field.options" :label="field.name" :name="field.key" :id="field.key" :hint="field.hint" v-model="inner"></v-checkboxes> <div class="relative">
<field-label :name="field.name" :required="false" :hint="field.hint" inline></field-label>
<div class="grid grid-cols-1 gap-2 pt-1">
<div class="text-sm text-gray-600" v-text="field.intro" v-if="field.intro"></div>
<label v-for="(option, index) in field.options" :key="index" :for="`${innerId}-${index}`" class="block relative flex items-start">
<input :id="`${innerId}-${index}`" v-model="inner" type="checkbox" :name="field.key" :value="option" class="peer absolute invisible" />
<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-[0.5rem] top-[0.5rem] w-2 h-2 absolute rounded block"></span>
<span class="pl-8 pt-1 @sm:pt-0 text-gray-600 text-sm @sm:text-base" v-text="option"></span>
</label>
</div>
</div>
</template> </template>
<script setup> <script setup>
import {computed} from 'vue'; import {computed} from 'vue';
import VCheckboxes from './VCheckboxes.vue'; import FieldLabel from '../FieldLabel.vue';
const emit = defineEmits(['update:modelValue']); const emit = defineEmits(['update:modelValue']);
const props = defineProps({ const props = defineProps({
@ -15,7 +26,7 @@ const props = defineProps({
field: { field: {
required: true, required: true,
validator: (value) => validator: (value) =>
hasKeys(value, [...globalFieldRules(), 'options', 'min', 'max']) && hasKeys(value, [...globalFieldRules(), 'options']) &&
typeof value.key === 'string' && typeof value.key === 'string' &&
value.key.length > 0 && value.key.length > 0 &&
typeof value.name === 'string' && typeof value.name === 'string' &&
@ -28,6 +39,8 @@ const props = defineProps({
}, },
}); });
const innerId = computed(() => (props.id ? props.id : props.field.key));
const inner = computed({ const inner = computed({
get: () => props.modelValue, get: () => props.modelValue,
set: (v) => emit('update:modelValue', v), set: (v) => emit('update:modelValue', v),

View File

@ -1,10 +1,26 @@
<template> <template>
<v-text type="date" :required="field.required" :name="field.key" :label="field.name" :id="innerId" :hint="field.hint" v-model="inner" :max-today="field.max_today"></v-text> <label class="w-full border border-solid border-gray-500 focus-within:border-primary rounded-lg relative flex">
<input
:id="innerId"
v-model="inner"
:name="field.key"
type="date"
:max="max"
placeholder=""
class="bg-white rounded-lg focus:outline-none text-gray-600 text-left py-1 px-2 @sm:py-2 text-sm @sm:text-base @sm:px-3 w-full"
/>
<div v-if="field.hint" class="absolute right-0 mr-2 flex items-center h-full">
<hint :value="field.hint"></hint>
</div>
<field-label :name="field.name" :required="field.required"></field-label>
</label>
</template> </template>
<script setup> <script setup>
import {computed} from 'vue'; import {computed} from 'vue';
import VText from './VText.vue'; import FieldLabel from '../FieldLabel.vue';
import dayjs from 'dayjs';
import Hint from '../Hint.vue';
const emit = defineEmits(['update:modelValue']); const emit = defineEmits(['update:modelValue']);
const props = defineProps({ const props = defineProps({
@ -31,6 +47,10 @@ const props = defineProps({
const innerId = computed(() => (props.id ? props.id : props.field.key)); const innerId = computed(() => (props.id ? props.id : props.field.key));
const max = computed(() => {
return props.field.max_today ? dayjs().format('YYYY-MM-DD') : null;
});
const inner = computed({ const inner = computed({
get: () => props.modelValue, get: () => props.modelValue,
set: (v) => emit('update:modelValue', v), set: (v) => emit('update:modelValue', v),

View File

@ -1,19 +1,23 @@
<template> <template>
<v-dropdown <label class="w-full border border-solid border-gray-500 focus-within:border-primary rounded-lg relative flex" :for="field.key">
v-model="inner" <select
:label="field.name" :id="innerId"
:id="field.key" v-model="inner"
:name="field.key" :disabled="disabled"
:options="options" :name="field.key"
:required="field.required" class="bg-white rounded-lg focus:outline-none text-gray-600 text-left peer py-1 px-2 @sm:py-2 text-sm @sm:text-base @sm:px-3 w-full"
:allowcustom="false" >
:empty-option-value="field.empty_option_value" <option :value="null">-- keine Angabe --</option>
></v-dropdown> <option v-for="(option, index) in options" :key="index" :value="option.id" v-text="option.name"></option>
<option v-if="field.has_empty_option" :value="-1" v-text="field.empty_option_value"></option>
</select>
<field-label :name="field.name" :required="field.required"></field-label>
</label>
</template> </template>
<script setup> <script setup>
import {computed, ref, watch} from 'vue'; import {computed, ref, watch} from 'vue';
import VDropdown from './VDropdown.vue'; import FieldLabel from '../FieldLabel.vue';
const emit = defineEmits(['update:modelValue']); const emit = defineEmits(['update:modelValue']);
const props = defineProps({ const props = defineProps({
@ -40,6 +44,8 @@ const props = defineProps({
}, },
}); });
const innerId = computed(() => (props.id ? props.id : props.field.key));
const inner = computed({ const inner = computed({
get: () => props.modelValue, get: () => props.modelValue,
set: (v) => emit('update:modelValue', v), set: (v) => emit('update:modelValue', v),

View File

@ -40,15 +40,7 @@
<div class="flex mt-4 space-x-3"> <div class="flex mt-4 space-x-3">
<v-text name="search_firstname" label="Vorname" id="search_firstname" v-model="searchData.vorname" @input="searchForMember(1)"></v-text> <v-text name="search_firstname" label="Vorname" id="search_firstname" v-model="searchData.vorname" @input="searchForMember(1)"></v-text>
<v-text name="search_lastname" label="Nachname" id="search_lastname" v-model="searchData.nachname" @input="searchForMember(1)"></v-text> <v-text name="search_lastname" label="Nachname" id="search_lastname" v-model="searchData.nachname" @input="searchForMember(1)"></v-text>
<v-dropdown <v-dropdown name="search_group" label="Stufe" id="search_group" v-model="searchData.untergliederungId" @input="searchForMember(1)" :options="eventMeta.agegroups"></v-dropdown>
name="search_group"
:allowcustom="false"
label="Stufe"
id="search_group"
v-model="searchData.untergliederungId"
@input="searchForMember(1)"
:options="eventMeta.agegroups"
></v-dropdown>
</div> </div>
<div class="relative min-h-48"> <div class="relative min-h-48">
<div class="relative grid grid-cols-[repeat(auto-fit,minmax(200px,1fr))] gap-2 mt-2"> <div class="relative grid grid-cols-[repeat(auto-fit,minmax(200px,1fr))] gap-2 mt-2">
@ -88,102 +80,35 @@
Hier siehst du noch einmal alle ausgewählten Mitglieder. Ggf sind hier pro Mitglied noch weitere Informationen erforderlich. Bitte gebe diese pro Mitglied an und klicke dann auf Hier siehst du noch einmal alle ausgewählten Mitglieder. Ggf sind hier pro Mitglied noch weitere Informationen erforderlich. Bitte gebe diese pro Mitglied an und klicke dann auf
"weiter". "weiter".
</p> </p>
<div class="grid gap-2 mt-6"> <div class="grid items-center gap-2 mt-6" :style="{'grid-template-columns': `max-content repeat(${memberFields.length}, 1fr)`}">
<accordion v-model="openValue" :index="index" :title="member.innerFormName" v-for="(member, index) in inner" :key="index" type="info"> <template v-for="member in inner" :key="member.id">
<template #buttons> <div v-text="member.innerFormName"></div>
<a @click.prevent.stop="deleteMember(index)" href="#"> <template v-for="(memberField, memberIndex) in memberFields">
<delete-icon class="w-3 h-3 text-red-700"></delete-icon> <v-checkbox
</a> v-if="memberField.type === 'CheckboxField'"
v-model="member[memberField.key]"
:name="`${field.key}-memberattr-${member.id}-${memberField.key}`"
:id="`${field.key}-memberattr-${member.id}-${memberField.key}`"
:label="memberField.name"
intro=""
></v-checkbox>
<v-dropdown
v-if="memberField.type === 'DropdownField'"
v-model="member[memberField.key]"
:name="`${field.key}-memberattr-${member.id}-${memberField.key}`"
:id="`${field.key}-memberattr-${member.id}-${memberField.key}`"
:label="memberField.name"
:options="
memberField.options.map((o) => {
return {id: o, name: o};
})
"
></v-dropdown>
</template> </template>
<div class="mt-2" :class="containerClassesSm"> </template>
<template v-for="(memberField, memberIndex) in member.id ? memberFields : newMemberFields">
<v-checkbox
v-if="memberField.type === 'CheckboxField'"
v-model="member[memberField.key]"
:name="`${field.key}-memberattr-${member.id}-${memberField.key}`"
:id="`${field.key}-memberattr-${member.id}-${memberField.key}`"
:label="memberField.description"
intro=""
:required="memberField.required"
:class="colClassesForField(memberField)"
></v-checkbox>
<v-dropdown
v-if="memberField.type === 'DropdownField'"
v-model="member[memberField.key]"
:name="`${field.key}-memberattr-${member.id}-${memberField.key}`"
:id="`${field.key}-memberattr-${member.id}-${memberField.key}`"
:label="memberField.name"
:allowcustom="false"
:class="colClassesForField(memberField)"
:required="memberField.required"
:options="
memberField.options.map((o) => {
return {id: o, name: o};
})
"
></v-dropdown>
<v-text
v-if="memberField.type === 'TextField' || memberField.type === 'EmailField'"
v-model="member[memberField.key]"
:name="`${field.key}-memberattr-${member.id}-${memberField.key}`"
:id="`${field.key}-memberattr-${member.id}-${memberField.key}`"
:label="memberField.name"
:required="memberField.required"
:class="colClassesForField(memberField)"
></v-text>
<v-text
v-if="memberField.type === 'DateField'"
v-model="member[memberField.key]"
:max-today="memberField.max_today"
:name="`${field.key}-memberattr-${member.id}-${memberField.key}`"
:id="`${field.key}-memberattr-${member.id}-${memberField.key}`"
:label="memberField.name"
:required="memberField.required"
:class="colClassesForField(memberField)"
type="date"
></v-text>
<v-text
v-if="memberField.type === 'NumberField'"
v-model="member[memberField.key]"
:min="memberField.min"
:max="memberField.max"
:name="`${field.key}-memberattr-${member.id}-${memberField.key}`"
:id="`${field.key}-memberattr-${member.id}-${memberField.key}`"
:label="memberField.name"
:required="memberField.required"
:class="colClassesForField(memberField)"
type="number"
></v-text>
<v-checkboxes
v-if="memberField.type === 'CheckboxesField'"
:intro="memberField.intro"
:options="memberField.options"
:label="memberField.name"
:name="`${field.key}-memberattr-${member.id}-${memberField.key}`"
:id="`${field.key}-memberattr-${member.id}-${memberField.key}`"
:hint="memberField.hint"
:class="colClassesForField(memberField)"
v-model="member[memberField.key]"
></v-checkboxes>
<v-radio
v-if="memberField.type === 'RadioField'"
:intro="memberField.intro"
:id="`${field.key}-memberattr-${member.id}-${memberField.key}`"
:required="memberField.required"
:allowcustom="memberField.allowcustom"
:options="memberField.options"
:label="memberField.name"
:hint="memberField.hint"
:class="colClassesForField(memberField)"
v-model="member[memberField.key]"
></v-radio>
</template>
</div>
</accordion>
</div> </div>
<div class="flex justify-center space-x-3 mt-5"> <div class="flex justify-center mt-5">
<v-btn @click.prevent="membersCompleted = true">Mitgliederdaten speichern</v-btn> <v-btn @click.prevent="membersCompleted = true">Mitgliederdaten speichern</v-btn>
<v-btn @click.prevent="addMember">Mitglied hinzufügen</v-btn>
</div> </div>
</template> </template>
<template #finished> <template #finished>
@ -198,24 +123,19 @@
<script setup> <script setup>
import {computed, ref, warn} from 'vue'; import {computed, ref, warn} from 'vue';
import VDropdown from './VDropdown.vue'; import FieldLabel from '../FieldLabel.vue';
import VCheckboxes from './VCheckboxes.vue';
import VText from './VText.vue'; import VText from './VText.vue';
import VDropdown from './VDropdown.vue';
import useFields from '../../composables/useFieldsWithoutNami.js'; import useFields from '../../composables/useFieldsWithoutNami.js';
import Info from '../Info.vue'; import Info from '../Info.vue';
import VBtn from '../VBtn.vue'; import VBtn from '../VBtn.vue';
import VCheckbox from './VCheckbox.vue'; import VCheckbox from './VCheckbox.vue';
import VRadio from './VRadio.vue';
import Spinner from '../Spinner.vue'; import Spinner from '../Spinner.vue';
import useAdremaLogin from '../../composables/useAdremaLogin.js'; import useAdremaLogin from '../../composables/useAdremaLogin.js';
import useEventMeta from '../../composables/useEventMeta.js'; import useEventMeta from '../../composables/useEventMeta.js';
import Pagination from '../Pagination.vue'; import Pagination from '../Pagination.vue';
import Accordion from '../Accordion.vue';
import useColumns from '../../composables/useColumns.js';
import DeleteIcon from '../icons/DeleteIcon.vue';
const eventMeta = useEventMeta(); const eventMeta = useEventMeta();
const {colClassesForField, containerClassesSm} = useColumns();
const {login, logout, user, loginData, searchData, searchForMember, resetSearchData, searchResults, searching} = useAdremaLogin(); const {login, logout, user, loginData, searchData, searchForMember, resetSearchData, searchResults, searching} = useAdremaLogin();
if (user.value !== null) { if (user.value !== null) {
@ -240,12 +160,6 @@ const step = computed(() => {
return 4; return 4;
}); });
const openValue = ref(-1);
function deleteMember(index) {
inner.value = inner.value.toSpliced(index, 1);
}
const membersAccepted = ref(false); const membersAccepted = ref(false);
const membersCompleted = ref(false); const membersCompleted = ref(false);
const emit = defineEmits(['update:modelValue']); const emit = defineEmits(['update:modelValue']);
@ -265,20 +179,12 @@ const props = defineProps({
const defaultMember = computed(() => { const defaultMember = computed(() => {
var fields = {}; var fields = {};
memberFields.value.forEach((field) => (fields[field.key] = field.value)); memberFields.value.forEach((field) => (fields[field.key] = field.default));
return fields;
});
const newDefaultMember = computed(() => {
var fields = {};
newMemberFields.value.forEach((field) => (fields[field.key] = field.value));
return fields; return fields;
}); });
const memberFields = computed(() => props.fields.filter((field) => field.for_members === true && field.nami_type === null)); const memberFields = computed(() => props.fields.filter((field) => field.for_members === true && field.nami_type === null));
const newMemberFields = computed(() => props.fields.filter((field) => field.for_members === true));
const inner = computed( const inner = computed(
{ {
@ -299,15 +205,11 @@ function memberSelected(member) {
return inner.value.map((m) => m.id).includes(member.id); return inner.value.map((m) => m.id).includes(member.id);
} }
function addMember(index) {
inner.value.push(JSON.parse(JSON.stringify({id: null, innerFormName: 'Neues Mitglied', ...newDefaultMember.value})));
}
function toggleMember(member) { function toggleMember(member) {
if (memberSelected(member)) { if (memberSelected(member)) {
inner.value = inner.value.filter((m) => m.id !== member.id); inner.value = inner.value.filter((m) => m.id !== member.id);
} else { } else {
inner.value.push(JSON.parse(JSON.stringify({id: member.id, innerFormName: member.name, ...defaultMember.value}))); inner.value.push({id: member.id, innerFormName: member.name, ...defaultMember.value});
} }
} }

View File

@ -1,10 +1,36 @@
<template> <template>
<v-radio :label="field.name" :options="field.options" :allowcustom="field.allowcustom" :intro="field.intro" v-model="inner" :hint="field.hint" :id="field.key" :required="field.required"></v-radio> <div class="relative">
<field-label :name="field.name" :required="field.required" :hint="field.hint" :button="modelValue === null ? '' : 'Auswahl löschen'" @buttonclick="selected = null" inline></field-label>
<div class="grid grid-cols-1 gap-2 pt-1">
<div class="text-sm text-gray-600" v-text="field.intro" v-if="field.intro"></div>
<label v-for="(option, index) in field.options" :key="index" :for="`${innerId}-${index}`" class="block relative flex items-center">
<input :id="`${innerId}-${index}`" v-model="selected" type="radio" :name="field.key" :value="option" class="peer absolute invisible" />
<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 text-gray-600 text-sm @sm:text-base" v-text="option"></span>
</label>
<label v-if="field.allowcustom" :for="`${innerId}-custom-value-selected`" class="block relative flex items-center">
<input :id="`${innerId}-custom-value-selected`" v-model="selected" type="radio" :name="field.key" value="custom-value-selected" class="peer absolute invisible" />
<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="ml-8 w-full border border-solid border-gray-500 focus-within:border-primary rounded-lg relative flex h-6">
<input
:id="`${innerId}-custom-value`"
v-model="customValue"
:name="`${innerId}-custom-value`"
placeholder="eigener Wert"
class="bg-white group-[.info]:bg-blue-200 rounded-lg focus:outline-none text-gray-600 text-left w-full py-1 @sm:py-2 @sm:group-[.info]:py-1 px-2 @sm:px-3 @sm:group-[.info]:px-2 text-sm @sm:text-base @sm:group-[.info]:text-sm"
/>
</span>
</label>
</div>
</div>
</template> </template>
<script setup> <script setup>
import {computed} from 'vue'; import {computed, ref, watch} from 'vue';
import VRadio from './VRadio.vue'; import FieldLabel from '../FieldLabel.vue';
const emit = defineEmits(['update:modelValue']); const emit = defineEmits(['update:modelValue']);
const props = defineProps({ const props = defineProps({
@ -25,10 +51,28 @@ const props = defineProps({
hasKeys(value.columns, ['mobile', 'desktop', 'tablet']) && hasKeys(value.columns, ['mobile', 'desktop', 'tablet']) &&
typeof value.options === 'object', typeof value.options === 'object',
}, },
id: {
required: false,
},
}); });
const inner = computed({ const innerId = computed(() => (props.id ? props.id : props.field.key));
get: () => props.modelValue,
set: (v) => emit('update:modelValue', v), const selected = ref(props.modelValue === null || props.field.options.includes(props.modelValue) ? props.modelValue : 'custom-value-selected');
watch(selected, (newValue) => {
customValue.value = newValue === 'custom-value-selected' ? '' : newValue;
});
const customValue = computed({
get: () => {
if (selected.value === 'custom-value-selected') {
return props.modelValue;
}
return '';
},
set: (v) => {
return emit('update:modelValue', v);
},
}); });
</script> </script>

View File

@ -1,5 +1,5 @@
<template> <template>
<v-text type="text" :required="field.required" :name="field.key" :label="field.name" :id="field.key" :hint="field.hint" v-model="inner"></v-text> <v-text :required="field.required" :name="field.key" :label="field.name" :id="innerId" :hint="field.hint" v-model="inner"></v-text>
</template> </template>
<script setup> <script setup>
@ -23,8 +23,13 @@ const props = defineProps({
value.name.length > 0 && value.name.length > 0 &&
hasKeys(value.columns, ['mobile', 'desktop', 'tablet']), hasKeys(value.columns, ['mobile', 'desktop', 'tablet']),
}, },
id: {
required: false,
},
}); });
const innerId = computed(() => (props.id ? props.id : props.field.key));
const inner = computed({ const inner = computed({
get: () => props.modelValue, get: () => props.modelValue,
set: (v) => emit('update:modelValue', v), set: (v) => emit('update:modelValue', v),

View File

@ -5,12 +5,12 @@
<label :for="id" class="p-0 block leading-none relative flex items-start"> <label :for="id" class="p-0 block leading-none relative flex items-start">
<input :id="id" v-model="inner" type="checkbox" :name="name" class="peer absolute invisible" /> <input :id="id" v-model="inner" type="checkbox" :name="name" class="peer absolute invisible" />
<span <span
class="border-neutral-400 border-4 group-[.box]:border-2 border-solid peer-checked:border-primary absolute left-0 w-6 h-6 group-[.box]:w-4 group-[.box]:h-4 group-[.box]:top-[5px] rounded block top-0" class="border-neutral-400 border-4 group-[.info]:border-2 border-solid peer-checked:border-primary absolute left-0 w-6 h-6 group-[.info]:w-4 group-[.info]:h-4 group-[.info]:top-[5px] rounded block top-0"
></span> ></span>
<span class="peer-checked:bg-primary left-[0.5rem] top-[0.5rem] group-[.box]:top-[0.58rem] group-[.box]:left-[0.25rem] w-2 h-2 absolute rounded block top-0"></span> <span class="peer-checked:bg-primary left-[0.5rem] top-[0.5rem] group-[.info]:top-[0.58rem] group-[.info]:left-[0.25rem] w-2 h-2 absolute rounded block top-0"></span>
<span v-if="label" class="pl-8 group-[.box]:pl-6 pt-1 @sm:pt-0 @sm:group-[.box]:pt-1 text-gray-600 text-sm @sm:text-base @sm:group-[.box]:text-sm"> <span v-if="label" class="pl-8 group-[.info]:pl-6 pt-1 @sm:pt-0 @sm:group-[.info]:pt-1 text-gray-600 text-sm @sm:text-base @sm:group-[.info]:text-sm">
<span v-text="label"></span> <span v-text="label"></span>
<span v-show="required" class="text-red-800"> *</span> <span v-show="required" class="text-red-800">*</span>
</span> </span>
<hint :value="hint" class="ml-2" v-if="hint"></hint> <hint :value="hint" class="ml-2" v-if="hint"></hint>
</label> </label>

View File

@ -1,60 +0,0 @@
<template>
<div class="relative">
<field-label :name="label" :required="false" :hint="hint" inline></field-label>
<div class="grid grid-cols-1 gap-2 pt-1 group-[.box]:gap-0">
<div class="text-sm text-gray-600" v-text="intro" v-if="intro"></div>
<label v-for="(option, index) in options" :key="index" :for="`${id}-${index}`" class="block relative flex items-start">
<input :id="`${id}-${index}`" v-model="inner" type="checkbox" :name="name" :value="option" class="peer absolute invisible" />
<span
class="border-neutral-400 border-4 group-[.box]:border-2 border-solid peer-checked:border-primary absolute left-0 w-6 h-6 group-[.box]:w-4 group-[.box]:h-4 group-[.box]:top-[5px] rounded block top-0"
></span>
<span class="peer-checked:bg-primary left-[0.5rem] top-[0.5rem] group-[.box]:top-[0.58rem] group-[.box]:left-[0.25rem] w-2 h-2 absolute rounded block top-0"></span>
<span class="pl-8 pt-1 @sm:pt-0 text-gray-600 text-sm @sm:text-base" v-text="option"></span>
</label>
</div>
</div>
</template>
<script setup>
import {computed} from 'vue';
import FieldLabel from '../FieldLabel.vue';
const emit = defineEmits(['update:modelValue']);
const props = defineProps({
modelValue: {
required: true,
},
name: {
required: true,
type: String,
},
id: {
required: true,
type: String,
},
label: {
required: true,
type: String,
},
options: {
required: true,
type: Object,
},
hint: {
required: false,
validator: (value) => value === null || typeof value === 'string',
default: () => null,
},
intro: {
required: false,
validator: (value) => value === null || typeof value === 'string',
default: () => null,
},
});
const inner = computed({
get: () => props.modelValue,
set: (v) => emit('update:modelValue', v),
});
</script>

View File

@ -7,16 +7,15 @@
:id="id" :id="id"
v-model="inner" v-model="inner"
:name="name" :name="name"
class="bg-white group-[.info]:bg-blue-200 rounded-lg focus:outline-none text-gray-600 text-left py-1 px-2 @sm:py-2 @sm:group-[.box]:py-1 text-sm @sm:text-base @sm:group-[.box]:text-sm @sm:px-3 @sm:group-[.box]:px-2 w-full" class="bg-white group-[.info]:bg-blue-200 rounded-lg focus:outline-none text-gray-600 text-left py-1 px-2 @sm:py-2 @sm:group-[.info]:py-1 text-sm @sm:text-base @sm:group-[.info]:text-sm @sm:px-3 @sm:group-[.info]:px-2 w-full"
> >
<option :value="null">-- keine Angabe --</option> <option :value="null">-- keine Angabe --</option>
<option v-for="(option, index) in options" :key="index" :value="option.id" v-text="option.name"></option> <option v-for="(option, index) in options" :key="index" :value="option.id" v-text="option.name"></option>
<option v-if="emptyOptionValue" :value="-1" v-text="emptyOptionValue"></option>
</select> </select>
<input <input
v-if="allowcustom" v-if="allowcustom"
v-model="inner" v-model="inner"
class="bg-white group-[.info]:bg-blue-200 rounded-lg focus:outline-none text-gray-600 text-left py-1 px-2 @sm:py-2 @sm:group-[.box]:py-1 text-sm @sm:text-base @sm:group-[.box]:text-sm @sm:px-3 @sm:group-[.box]:px-2 w-full" class="bg-white group-[.info]:bg-blue-200 rounded-lg focus:outline-none text-gray-600 text-left py-1 px-2 @sm:py-2 @sm:group-[.info]:py-1 text-sm @sm:text-base @sm:group-[.info]:text-sm @sm:px-3 @sm:group-[.info]:px-2 w-full"
type="text" type="text"
:list="`${id}-list`" :list="`${id}-list`"
/> />
@ -26,7 +25,7 @@
<div v-if="hint" class="absolute right-0 mr-2 flex items-center h-full"> <div v-if="hint" class="absolute right-0 mr-2 flex items-center h-full">
<hint :value="hint"></hint> <hint :value="hint"></hint>
</div> </div>
<field-label :name="label" :required="required"></field-label> <field-label :name="label" class="group-[.info]:bg-blue-200" :required="required"></field-label>
</label> </label>
</div> </div>
</template> </template>
@ -40,7 +39,7 @@ const emit = defineEmits(['update:modelValue']);
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {
required: true, required: true,
validator: (value) => value === null || typeof value === 'string' || typeof value === 'number', validator: (value) => value === null || typeof value === 'string',
}, },
required: { required: {
required: false, required: false,
@ -51,10 +50,6 @@ const props = defineProps({
required: true, required: true,
type: String, type: String,
}, },
emptyOptionValue: {
required: false,
default: () => '',
},
id: { id: {
required: true, required: true,
type: String, type: String,

View File

@ -1,85 +0,0 @@
<template>
<div class="relative">
<field-label :name="label" :required="required" :hint="hint" :button="modelValue === null ? '' : 'Auswahl löschen'" @buttonclick="selected = null" inline></field-label>
<div class="grid grid-cols-1 gap-2 group-[.box]:gap-0 pt-1">
<div class="text-sm text-gray-600" v-text="intro" v-if="intro"></div>
<label v-for="(option, index) in innerOptions" :key="index" :for="`${id}-${index}`" class="block relative flex items-center">
<input :id="`${id}-${index}`" v-model="selected" type="radio" :name="id" :value="option.id" class="peer absolute invisible" />
<span
class="border-neutral-400 border-4 group-[.box]:border-2 border-solid peer-checked:border-primary absolute left-0 w-6 h-6 group-[.box]:w-4 group-[.box]:h-4 group-[.box]:top-[5px] rounded-full block top-0"
></span>
<span class="peer-checked:bg-primary left-2 group-[.box]:top-[0.58rem] group-[.box]:left-[0.25rem] w-2 h-2 absolute rounded-full block"></span>
<span v-if="option.iscustom" class="ml-8 w-full border border-solid border-gray-500 focus-within:border-primary rounded-lg relative flex h-6">
<input
:id="`${id}-custom-value`"
v-model="customValue"
:name="`${id}-custom-value`"
placeholder="eigener Wert"
class="bg-white group-[.info]:bg-blue-200 rounded-lg focus:outline-none text-gray-600 text-left w-full py-1 @sm:py-2 @sm:group-[.box]:py-1 px-2 @sm:px-3 @sm:group-[.box]:px-2 text-sm @sm:text-base @sm:group-[.box]:text-sm"
/>
</span>
<span v-else class="pl-8 text-gray-600 text-sm @sm:text-base" v-text="option.name"></span>
</label>
</div>
</div>
</template>
<script setup>
import {computed, ref, watch} from 'vue';
import FieldLabel from '../FieldLabel.vue';
const emit = defineEmits(['update:modelValue']);
const props = defineProps({
label: {
required: true,
},
required: {
required: true,
},
hint: {
required: true,
},
modelValue: {
required: true,
validator: (value) => value === null || typeof value === 'string',
},
intro: {
required: true,
},
options: {
required: true,
},
allowcustom: {
required: true,
},
id: {
required: true,
},
});
const innerOptions = computed(() => {
var fieldValues = props.options.map(function (option) {
return {id: option, name: option, iscustom: false};
});
if (props.allowcustom) {
fieldValues.push({id: 'custom-value-selected', iscustom: true});
}
return fieldValues;
});
const optionIds = computed(() => innerOptions.value.map((option) => option.id));
const selected = ref(props.modelValue === null || optionIds.value.includes(props.modelValue) ? props.modelValue : 'custom-value-selected');
watch(selected, (newValue) => {
customValue.value = newValue === 'custom-value-selected' ? '' : newValue;
});
const customValue = computed({
get: () => (selected.value === 'custom-value-selected' ? props.modelValue : ''),
set: (v) => {
return emit('update:modelValue', v);
},
});
</script>

View File

@ -5,16 +5,16 @@
v-model="inner" v-model="inner"
:name="name" :name="name"
:min="min" :min="min"
:max="presentMax" :max="max"
:type="type" :type="type"
placeholder="" placeholder=""
class="bg-white group-[.info]:bg-blue-200 rounded-lg focus:outline-none text-gray-600 text-left w-full py-1 @sm:py-2 @sm:group-[.box]:py-1 px-2 @sm:px-3 @sm:group-[.box]:px-2 text-sm @sm:text-base @sm:group-[.box]:text-sm" class="bg-white group-[.info]:bg-blue-200 rounded-lg focus:outline-none text-gray-600 text-left w-full py-1 @sm:py-2 @sm:group-[.info]:py-1 px-2 @sm:px-3 @sm:group-[.info]:px-2 text-sm @sm:text-base @sm:group-[.info]:text-sm"
@keypress="$emit('keypress', $event)" @keypress="$emit('keypress', $event)"
/> />
<div v-if="hint" class="absolute right-0 mr-2 flex items-center h-full"> <div v-if="hint" class="absolute right-0 mr-2 flex items-center h-full">
<hint :value="hint"></hint> <hint :value="hint"></hint>
</div> </div>
<field-label :name="label" :required="required"></field-label> <field-label :name="label" class="group-[.info]:bg-blue-200" :required="required"></field-label>
</label> </label>
</template> </template>
@ -22,13 +22,12 @@
import {computed} from 'vue'; import {computed} from 'vue';
import FieldLabel from '../FieldLabel.vue'; import FieldLabel from '../FieldLabel.vue';
import Hint from '../Hint.vue'; import Hint from '../Hint.vue';
import dayjs from 'dayjs';
const emit = defineEmits(['update:modelValue', 'keypress']); const emit = defineEmits(['update:modelValue', 'keypress']);
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {
required: true, required: true,
validator: (value) => value === null || typeof value === 'string' || typeof value === 'number', validator: (value) => value === null || typeof value === 'string',
}, },
required: { required: {
required: false, required: false,
@ -44,22 +43,17 @@ const props = defineProps({
type: String, type: String,
}, },
min: { min: {
type: Number, type: String,
default: () => undefined, default: () => '',
}, },
max: { max: {
type: Number, type: String,
default: () => undefined, default: () => '',
}, },
label: { label: {
required: true, required: true,
type: String, type: String,
}, },
maxToday: {
required: false,
type: Boolean,
default: () => false,
},
type: { type: {
required: false, required: false,
type: String, type: String,
@ -72,18 +66,6 @@ const props = defineProps({
}, },
}); });
const presentMax = computed(() => {
if (props.type === 'date' && props.maxToday) {
return dayjs().format('YYYY-MM-DD');
}
if (props.type === 'number') {
return props.max;
}
return null;
});
const inner = computed({ const inner = computed({
get: () => props.modelValue, get: () => props.modelValue,
set: (v) => emit('update:modelValue', v), set: (v) => emit('update:modelValue', v),

View File

@ -9,14 +9,13 @@
viewBox="0 0 512 512" viewBox="0 0 512 512"
style="enable-background: new 0 0 512 512" style="enable-background: new 0 0 512 512"
xml:space="preserve" xml:space="preserve"
fill="currentColor"
> >
<g> <g>
<g> <g>
<path <path
d="M486.4,102.4h-128V25.6c0-15.36-10.24-25.6-25.6-25.6H179.2c-15.36,0-25.6,10.24-25.6,25.6v76.8h-128 d="M486.4,102.4h-128V25.6c0-15.36-10.24-25.6-25.6-25.6H179.2c-15.36,0-25.6,10.24-25.6,25.6v76.8h-128
C10.24,102.4,0,112.64,0,128s10.24,25.6,25.6,25.6h460.8c15.36,0,25.6-10.24,25.6-25.6S501.76,102.4,486.4,102.4z M307.2,102.4 C10.24,102.4,0,112.64,0,128s10.24,25.6,25.6,25.6h460.8c15.36,0,25.6-10.24,25.6-25.6S501.76,102.4,486.4,102.4z M307.2,102.4
H204.8V51.2h102.4V102.4z" H204.8V51.2h102.4V102.4z"
/> />
</g> </g>
</g> </g>
@ -24,10 +23,10 @@
<g> <g>
<path <path
d="M25.6,204.8l48.64,284.16c2.56,12.8,12.8,23.04,25.6,23.04h312.32c12.8,0,23.04-10.24,25.6-23.04L486.4,204.8H25.6z d="M25.6,204.8l48.64,284.16c2.56,12.8,12.8,23.04,25.6,23.04h312.32c12.8,0,23.04-10.24,25.6-23.04L486.4,204.8H25.6z
M153.6,460.8c-15.36,0-25.6-10.24-25.6-25.6l-25.6-153.6c0-15.36,10.24-25.6,25.6-25.6s25.6,10.24,25.6,25.6l25.6,153.6 M153.6,460.8c-15.36,0-25.6-10.24-25.6-25.6l-25.6-153.6c0-15.36,10.24-25.6,25.6-25.6s25.6,10.24,25.6,25.6l25.6,153.6
C179.2,450.56,168.96,460.8,153.6,460.8z M281.6,435.2c0,15.36-10.24,25.6-25.6,25.6s-25.6-10.24-25.6-25.6V281.6 C179.2,450.56,168.96,460.8,153.6,460.8z M281.6,435.2c0,15.36-10.24,25.6-25.6,25.6s-25.6-10.24-25.6-25.6V281.6
c0-15.36,10.24-25.6,25.6-25.6s25.6,10.24,25.6,25.6V435.2z M384,435.2c0,15.36-10.24,25.6-25.6,25.6 c0-15.36,10.24-25.6,25.6-25.6s25.6,10.24,25.6,25.6V435.2z M384,435.2c0,15.36-10.24,25.6-25.6,25.6
c-15.36,0-25.6-10.24-25.6-25.6l25.6-153.6c0-15.36,10.24-25.6,25.6-25.6s25.6,10.24,25.6,25.6L384,435.2z" c-15.36,0-25.6-10.24-25.6-25.6l25.6-153.6c0-15.36,10.24-25.6,25.6-25.6s25.6,10.24,25.6,25.6L384,435.2z"
/> />
</g> </g>
</g> </g>

View File

@ -1,49 +0,0 @@
import {ref} from 'vue';
export default function useColumns() {
const colClasses = {
mobile: {
1: 'col-span-1',
2: 'col-span-2',
3: 'col-span-3',
4: 'col-span-4',
5: 'col-span-5',
6: 'col-span-6',
},
tablet: {
1: '@sm:col-span-1',
2: '@sm:col-span-2',
3: '@sm:col-span-3',
4: '@sm:col-span-4',
5: '@sm:col-span-5',
6: '@sm:col-span-6',
},
desktop: {
1: '@lg:col-span-1',
2: '@lg:col-span-2',
3: '@lg:col-span-3',
4: '@lg:col-span-4',
5: '@lg:col-span-5',
6: '@lg:col-span-6',
},
};
const containerClassesDefault = 'grid grid-cols-2 @sm:grid-cols-4 @lg:grid-cols-6 items-start';
const containerClassesSm = containerClassesDefault + ' gap-4';
const containerClasses = containerClassesDefault + ' gap-6';
function colClassesForField(field) {
return {
[colClasses.mobile[field.columns.mobile]]: true,
[colClasses.tablet[field.columns.tablet]]: true,
[colClasses.desktop[field.columns.desktop]]: true,
};
}
return {
colClasses,
colClassesForField,
containerClasses,
containerClassesSm,
};
}