Compare commits

...

11 Commits

Author SHA1 Message Date
philipp lang 5661d66770 Add search result component
continuous-integration/drone/push Build is passing Details
2024-07-03 17:43:49 +02:00
philipp lang 0a8ecedb0f Update search 2024-07-03 17:31:06 +02:00
philipp lang fd9aff25fe Add hits to searchModal 2024-07-03 17:20:47 +02:00
philipp lang c65a25ed2a Fix: Switch input should not be invisible 2024-07-03 17:17:44 +02:00
philipp lang ced2d577c0 Lint 2024-07-03 17:12:57 +02:00
philipp lang aafef695aa Lint 2024-07-03 17:12:32 +02:00
philipp lang 44a39fc5a8 Lint 2024-07-03 17:10:04 +02:00
philipp lang 46a353d674 Fix event name im contribution 2024-07-03 17:09:34 +02:00
philipp lang fed0e95f14 Fix switch with array 2024-07-03 17:07:14 +02:00
philipp lang d1dfcb3a04 Add dev url for search 2024-07-03 17:03:53 +02:00
philipp lang 7f9be4262c Lint 2024-07-03 16:37:58 +02:00
32 changed files with 221 additions and 242 deletions

View File

@ -2,7 +2,7 @@
<label class="flex flex-col items-start group" :for="id" :class="sizeClass(size)"> <label class="flex flex-col items-start group" :for="id" :class="sizeClass(size)">
<f-label v-if="label" :required="false" :value="label"></f-label> <f-label v-if="label" :required="false" :value="label"></f-label>
<span class="relative flex-none flex" :class="{'pr-8': hint, [fieldHeight]: true}"> <span class="relative flex-none flex" :class="{'pr-8': hint, [fieldHeight]: true}">
<input :id="id" v-model="v" type="checkbox" :name="name" :value="value" :disabled="disabled" class="absolute peer invisible" @keypress="$emit('keypress', $event)" /> <input :id="id" v-model="v" type="checkbox" :name="name" :value="value" :disabled="disabled" class="absolute peer opacity-0" @keypress="$emit('keypress', $event)" />
<span <span
class="relative cursor-pointer h-full rounded peer-focus:bg-red-500 transition-all duration-300 group-[.field-base]:w-[70px] group-[.field-sm]:w-[46px] bg-gray-700 peer-checked:bg-primary-700" class="relative cursor-pointer h-full rounded peer-focus:bg-red-500 transition-all duration-300 group-[.field-base]:w-[70px] group-[.field-sm]:w-[46px] bg-gray-700 peer-checked:bg-primary-700"
></span> ></span>
@ -80,9 +80,9 @@ const v = computed({
return; return;
} }
var a = props.modelValue.filter((i) => i !== this.value); var a = props.modelValue.filter((i) => i !== props.value);
if (v) { if (v) {
a.push(this.value); a.push(props.value);
} }
emit('update:modelValue', a); emit('update:modelValue', a);

View File

@ -1,70 +1,40 @@
<template> <template>
<div class="fixed z-40 top-0 left-0 w-full h-full flex items-start pt-12 justify-center p-6 bg-black/60" <div class="fixed z-40 top-0 left-0 w-full h-full flex items-start pt-12 justify-center p-6 bg-black/60" @click.self="emit('close')">
@click.self="emit('close')"> <div class="relative rounded-lg p-4 sm:p-8 shadow-2xl shadow-black border border-sky-700/30 bg-sky-800/10 border-solid w-full max-w-[50rem] overflow-auto backdrop-blur-lg">
<div
class="relative rounded-lg p-4 sm:p-8 shadow-2xl shadow-black border border-sky-700/30 bg-sky-800/10 border-solid w-full max-w-[50rem] overflow-auto backdrop-blur-lg">
<div class="relative"> <div class="relative">
<input ref="searchInput" v-model="searchString" type="text" <input
ref="searchInput"
v-model="searchString"
type="text"
class="w-full px-3 sm:px-5 py-2 sm:py-3 pl-12 sm:pl-16 rounded-xl sm:rounded-2xl border-sky-200/40 text-sky-200 sm:text-xl placeholder-sky-200/40 bg-sky-600/20" class="w-full px-3 sm:px-5 py-2 sm:py-3 pl-12 sm:pl-16 rounded-xl sm:rounded-2xl border-sky-200/40 text-sky-200 sm:text-xl placeholder-sky-200/40 bg-sky-600/20"
placeholder="Wer suchet, der findet …" /> placeholder="Wer suchet, der findet …"
/>
<div class="absolute flex items-center h-full top-0 left-4"> <div class="absolute flex items-center h-full top-0 left-4">
<ui-sprite src="search" class="w-5 h-5 sm:w-7 sm:h-7 text-sky-200/20" /> <ui-sprite src="search" class="w-5 h-5 sm:w-7 sm:h-7 text-sky-200/20" />
</div> </div>
</div> </div>
<div v-if="results !== null" class="mt-5 sm:mt-10 space-y-2"> <div v-if="results.hits.length" class="mt-5 sm:mt-10 space-y-2">
<div v-for="member in results.hits" :key="member.id"> <ui-search-result v-for="member in results.hits" :key="member.id" :member="member">
<div <template #buttons>
class="flex items-center justify-between hover:bg-sky-600/20 transition text-sky-300 px-3 sm:px-6 py-1 sm:py-3 rounded-lg"> <i-link v-tooltip="`Details`" :href="member.links.show" class="inline-flex btn btn-primary btn-sm" @click="emit('close')"><ui-sprite src="eye"></ui-sprite></i-link>
<div class="flex space-x-2 items-center"> <i-link v-tooltip="`Bearbeiten`" :href="member.links.edit" class="inline-flex btn btn-warning btn-sm" @click="emit('close')"><ui-sprite src="pencil"></ui-sprite></i-link>
<div class="w-5 sm:w-16 flex flex-none"> </template>
<ui-age-groups icon-class="w-4 h-4 sm:w-6 sm:h-6" class="flex-col sm:flex-row" </ui-search-result>
:member="member"></ui-age-groups>
</div>
<div class="flex items-baseline flex-col md:flex-row">
<span class="text-lg" v-text="member.fullname"></span>
<span class="ml-2 text-xs" v-text="member.group_name"></span>
</div>
</div>
<div class="flex space-x-2">
<i-link v-tooltip="`Details`" :href="member.links.show"
class="inline-flex btn btn-primary btn-sm" @click="emit('close')"><ui-sprite
src="eye"></ui-sprite></i-link>
<i-link v-tooltip="`Bearbeiten`" :href="member.links.edit"
class="inline-flex btn btn-warning btn-sm" @click="emit('close')"><ui-sprite
src="pencil"></ui-sprite></i-link>
</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script setup> <script lang="js" setup>
import { computed, ref, onMounted } from 'vue'; import { ref, onMounted } from 'vue';
import useSearch from '../../composables/useSearch.js'; import useSearch from '../../composables/useSearch.js';
const emit = defineEmits(['close']); const emit = defineEmits(['close']);
const { search } = useSearch(); const { searchString, results } = useSearch(null, { limit: 10 });
const realSearchString = ref('');
const results = ref(null);
const searchInput = ref(null); const searchInput = ref(null);
onMounted(() => { onMounted(() => {
searchInput.value.focus(); searchInput.value.focus();
}); });
const searchString = computed({
get: () => realSearchString.value,
set: async (v) => {
realSearchString.value = v;
if (!v.length) {
results.value = null;
return;
}
results.value = await search(v, [], { limit: 10 });
},
});
</script> </script>

View File

@ -0,0 +1,29 @@
<template>
<div>
<div class="flex items-center justify-between hover:bg-sky-600/20 transition text-sky-300 px-3 sm:px-6 py-1 sm:py-3 rounded-lg">
<div class="flex space-x-2 items-center">
<div class="w-5 sm:w-16 flex flex-none">
<ui-age-groups icon-class="w-4 h-4 sm:w-6 sm:h-6" class="flex-col sm:flex-row" :member="member"></ui-age-groups>
</div>
<div class="flex items-baseline flex-col md:flex-row">
<span class="text-lg" v-text="member.fullname"></span>
<span class="ml-2 text-xs" v-text="member.group_name"></span>
</div>
</div>
<div class="flex space-x-2">
<slot name="buttons"></slot>
</div>
</div>
</div>
</template>
<script lang="js" setup>
defineProps({
member: {
type: Object,
required: true
},
});
const emit = defineEmits(['click']);
</script>

View File

@ -1,10 +1,15 @@
import {inject} from 'vue'; import {inject, computed, ref} from 'vue';
export default function useSearch() { export default function useSearch(params = null, options = null) {
params = params === null ? [] : params;
options = options === null ? {} : options;
const axios = inject('axios'); const axios = inject('axios');
const results = ref({hits: []});
const realSearchString = ref('');
async function search(text, filters = [], options = {}) { async function search(text, filters = [], options = {}) {
var response = await axios.post( var response = await axios.post(
'/indexes/members/search', import.meta.env.MODE === 'development' ? 'http://localhost:7700/indexes/members/search' : '/indexes/members/search',
{ {
q: text, q: text,
filter: filters, filter: filters,
@ -17,7 +22,28 @@ export default function useSearch() {
return response.data; return response.data;
} }
function clearSearch() {
searchString.value = '';
}
const searchString = computed({
get: () => realSearchString.value,
set: async (v) => {
realSearchString.value = v;
if (!v.length) {
results.value = {hits: []};
return;
}
results.value = await search(v, params, options);
},
});
return { return {
search, search,
searchString,
results,
clearSearch,
}; };
} }

View File

@ -36,7 +36,7 @@
</page-layout> </page-layout>
</template> </template>
<script setup> <script lang="js" setup>
import { ref, defineProps } from 'vue'; import { ref, defineProps } from 'vue';
import { indexProps, useIndex } from '../../composables/useIndex.js'; import { indexProps, useIndex } from '../../composables/useIndex.js';

View File

@ -9,10 +9,10 @@
<f-select id="country" v-model="values.country" :options="countries" name="country" label="Land" required></f-select> <f-select id="country" v-model="values.country" :options="countries" name="country" label="Land" required></f-select>
<div class="border-gray-200 shadow shadow-primary-700 p-3 shadow-[0_0_4px_gray] col-span-2"> <div class="border-gray-200 shadow shadow-primary-700 p-3 shadow-[0_0_4px_gray] col-span-2">
<f-text id="search_text" ref="searchTextField" v-model="searchText" class="col-span-2" label="Suchen …" size="sm" @keypress.enter.prevent="onSubmitFirstMemberResult"></f-text> <f-text id="search_text" ref="searchInput" v-model="searchString" class="col-span-2" label="Suchen …" size="sm" @keypress.enter.prevent="onSubmitFirstMemberResult"></f-text>
<div class="mt-2 grid grid-cols-[repeat(auto-fill,minmax(180px,1fr))] gap-2 col-span-2"> <div class="mt-2 grid grid-cols-[repeat(auto-fill,minmax(180px,1fr))] gap-2 col-span-2">
<f-switch <f-switch
v-for="member in results" v-for="member in results.hits"
:id="`members-${member.id}`" :id="`members-${member.id}`"
:key="member.id" :key="member.id"
v-model="values.members" v-model="values.members"
@ -26,17 +26,17 @@
</div> </div>
</div> </div>
<button v-for="(compiler, index) in compilers" class="btn btn-primary mt-3 inline-block" @click.prevent="submit(compiler.class)" v-text="compiler.title"></button> <button v-for="(compiler, index) in compilers" :key="index" class="btn btn-primary mt-3 inline-block" @click.prevent="submit(compiler.class)" v-text="compiler.title"></button>
</form> </form>
</page-layout> </page-layout>
</template> </template>
<script setup> <script lang="js" setup>
import {ref, computed, inject} from 'vue'; import { ref, inject } from 'vue';
import useSearch from '../../composables/useSearch.js'; import useSearch from '../../composables/useSearch.js';
const axios = inject('axios'); const axios = inject('axios');
const {search} = useSearch(); const { searchString, results, clearSearch } = useSearch(['birthday IS NOT NULL', 'address IS NOT EMPTY']);
const props = defineProps({ const props = defineProps({
data: {}, data: {},
@ -44,13 +44,11 @@ const props = defineProps({
compilers: {}, compilers: {},
}); });
const searchRaw = ref(''); const searchInput = ref([]);
const results = ref([]);
const searchTextField = ref([]);
const values = ref({ const values = ref({
type: null, type: null,
members: [], members: [],
event_name: '', eventName: '',
dateFrom: '', dateFrom: '',
dateUntil: '', dateUntil: '',
zipLocation: '', zipLocation: '',
@ -58,15 +56,6 @@ const values = ref({
...props.data, ...props.data,
}); });
const searchText = computed({
get: () => searchRaw.value,
set: async (v) => {
searchRaw.value = v;
results.value = (await search(v, ['birthday IS NOT NULL', 'address IS NOT EMPTY'])).hits;
},
});
async function submit(compiler) { async function submit(compiler) {
values.value.type = compiler; values.value.type = compiler;
await axios.post('/contribution-validate', values.value); await axios.post('/contribution-validate', values.value);
@ -80,15 +69,15 @@ function onSubmitMemberResult(selected) {
values.value.members.push(selected.id); values.value.members.push(selected.id);
} }
searchRaw.value = ''; clearSearch();
searchTextField.value.$el.querySelector('input').focus(); searchInput.value.$el.querySelector('input').focus();
} }
function onSubmitFirstMemberResult() { function onSubmitFirstMemberResult() {
if (results.value.length === 0) { if (results.value.hits.length === 0) {
searchRaw.value = ''; clearSearch();
return; return;
} }
onSubmitMemberResult(results.value[0]); onSubmitMemberResult(results.value.hits[0]);
} }
</script> </script>

View File

@ -75,7 +75,7 @@
</page-layout> </page-layout>
</template> </template>
<script setup> <script lang="js" setup>
import { useApiIndex } from '../../composables/useApiIndex.js'; import { useApiIndex } from '../../composables/useApiIndex.js';
import SettingLayout from '../setting/Layout.vue'; import SettingLayout from '../setting/Layout.vue';

View File

@ -55,7 +55,7 @@
</div> </div>
</template> </template>
<script setup> <script lang="js" setup>
import { ref, inject, computed } from 'vue'; import { ref, inject, computed } from 'vue';
const axios = inject('axios'); const axios = inject('axios');
const emit = defineEmits(['save']); const emit = defineEmits(['save']);

View File

@ -172,7 +172,7 @@
</page-layout> </page-layout>
</template> </template>
<script setup> <script lang="js" setup>
import { ref, inject, computed } from 'vue'; import { ref, inject, computed } from 'vue';
import { indexProps, useIndex } from '../../composables/useInertiaApiIndex.js'; import { indexProps, useIndex } from '../../composables/useInertiaApiIndex.js';
import FormBuilder from '../formtemplate/FormBuilder.vue'; import FormBuilder from '../formtemplate/FormBuilder.vue';

View File

@ -4,43 +4,18 @@
<f-text id="search_string" v-model="searchString" label="Mitglied finden"></f-text> <f-text id="search_string" v-model="searchString" label="Mitglied finden"></f-text>
</div> </div>
<div v-if="results !== null" class="mt-5 sm:mt-10 space-y-2"> <div v-if="results !== null" class="mt-5 sm:mt-10 space-y-2">
<a v-for="member in results.hits" :key="member.id" href="#" @click.prevent="emit('assign', member.id)"> <ui-search-result v-for="member in results.hits" :key="member.id" :member="member">
<div class="flex items-center justify-between hover:bg-sky-600/20 transition text-sky-300 px-3 sm:px-6 py-1 sm:py-3 rounded-lg"> <template #buttons>
<div class="flex space-x-2 items-center"> <button class="btn btn-primary btn-sm" @click.prevent="emit('assign', member.id)">Zuweisen</button>
<div class="w-5 sm:w-16 flex flex-none"> </template>
<ui-age-groups icon-class="w-4 h-4 sm:w-6 sm:h-6" class="flex-col sm:flex-row" :member="member"></ui-age-groups> </ui-search-result>
</div>
<div class="flex items-baseline flex-col md:flex-row">
<span class="text-lg" v-text="member.fullname"></span>
<span class="ml-2 text-xs" v-text="member.group_name"></span>
</div>
</div>
</div>
</a>
</div> </div>
</div> </div>
</template> </template>
<script setup> <script lang="js" setup>
import {computed, ref} from 'vue';
import useSearch from '../../composables/useSearch.js'; import useSearch from '../../composables/useSearch.js';
const emit = defineEmits(['assign']); const emit = defineEmits(['assign']);
const {search} = useSearch(); const { searchString, results } = useSearch(null, { limit: 10 });
const realSearchString = ref('');
const results = ref(null);
const searchString = computed({
get: () => realSearchString.value,
set: async (v) => {
realSearchString.value = v;
if (!v.length) {
results.value = null;
return;
}
results.value = await search(v, [], {limit: 10});
},
});
</script> </script>

View File

@ -108,7 +108,7 @@
</div> </div>
</template> </template>
<script setup> <script lang="js" setup>
import { watch, ref, computed } from 'vue'; import { watch, ref, computed } from 'vue';
import { useApiIndex } from '../../composables/useApiIndex.js'; import { useApiIndex } from '../../composables/useApiIndex.js';
import useTableToggle from '../../composables/useTableToggle.js'; import useTableToggle from '../../composables/useTableToggle.js';

View File

@ -14,7 +14,7 @@
</ui-box> </ui-box>
</template> </template>
<script setup> <script lang="js" setup>
const emit = defineEmits(['close', 'submit']); const emit = defineEmits(['close', 'submit']);
const props = defineProps({ const props = defineProps({

View File

@ -17,7 +17,7 @@
></f-textarea> ></f-textarea>
</template> </template>
<script setup> <script lang="js" setup>
const props = defineProps({ const props = defineProps({
modelValue: {}, modelValue: {},
meta: {}, meta: {},

View File

@ -30,7 +30,7 @@
</div> </div>
</template> </template>
<script setup> <script lang="js" setup>
import useElements from './useElements.js'; import useElements from './useElements.js';
const { addOption, setOption, removeOption } = useElements(); const { addOption, setOption, removeOption } = useElements();

View File

@ -42,7 +42,7 @@
</div> </div>
</template> </template>
<script setup> <script lang="js" setup>
defineEmits(['update:modelValue']); defineEmits(['update:modelValue']);
const props = defineProps({ const props = defineProps({

View File

@ -19,7 +19,7 @@
></f-switch> ></f-switch>
</template> </template>
<script setup> <script lang="js" setup>
const props = defineProps({ const props = defineProps({
modelValue: {}, modelValue: {},
meta: {}, meta: {},

View File

@ -71,7 +71,7 @@
</form> </form>
</template> </template>
<script setup> <script lang="js" setup>
import { watch, computed, ref } from 'vue'; import { watch, computed, ref } from 'vue';
import { snakeCase } from 'change-case'; import { snakeCase } from 'change-case';
import '!/adrema-form/dist/main.js'; import '!/adrema-form/dist/main.js';

View File

@ -45,7 +45,7 @@
></f-select> ></f-select>
</template> </template>
<script setup> <script lang="js" setup>
import { computed } from 'vue'; import { computed } from 'vue';
const props = defineProps({ const props = defineProps({

View File

@ -58,7 +58,7 @@
</page-layout> </page-layout>
</template> </template>
<script setup> <script lang="js" setup>
import { ref } from 'vue'; import { ref } from 'vue';
import { indexProps, useIndex } from '../../composables/useInertiaApiIndex.js'; import { indexProps, useIndex } from '../../composables/useInertiaApiIndex.js';
import FormBuilder from './FormBuilder.vue'; import FormBuilder from './FormBuilder.vue';

View File

@ -12,7 +12,7 @@
<f-text id="max" :model-value="modelValue.max" label="maximaler Wert" size="sm" type="number" @update:modelValue="$emit('update:modelValue', {...modelValue, max: parse($event)})"></f-text> <f-text id="max" :model-value="modelValue.max" label="maximaler Wert" size="sm" type="number" @update:modelValue="$emit('update:modelValue', {...modelValue, max: parse($event)})"></f-text>
</template> </template>
<script setup> <script lang="js" setup>
const props = defineProps({ const props = defineProps({
modelValue: {}, modelValue: {},
meta: {}, meta: {},

View File

@ -38,7 +38,7 @@
></f-switch> ></f-switch>
</template> </template>
<script setup> <script lang="js" setup>
import useElements from './useElements.js'; import useElements from './useElements.js';
const { addOption, setOption, removeOption } = useElements(); const { addOption, setOption, removeOption } = useElements();

View File

@ -10,7 +10,7 @@
></f-switch> ></f-switch>
</template> </template>
<script setup> <script lang="js" setup>
const props = defineProps({ const props = defineProps({
modelValue: {}, modelValue: {},
meta: {}, meta: {},

View File

@ -11,7 +11,7 @@
></f-switch> ></f-switch>
</template> </template>
<script setup> <script lang="js" setup>
const props = defineProps({ const props = defineProps({
modelValue: {}, modelValue: {},
meta: {}, meta: {},

View File

@ -92,7 +92,7 @@
</page-layout> </page-layout>
</template> </template>
<script setup> <script lang="js" setup>
import { ref } from 'vue'; import { ref } from 'vue';
import { indexProps, useIndex } from '../../composables/useInertiaApiIndex.js'; import { indexProps, useIndex } from '../../composables/useInertiaApiIndex.js';
import useTableToggle from '../../composables/useTableToggle.js'; import useTableToggle from '../../composables/useTableToggle.js';

View File

@ -124,7 +124,7 @@
</page-layout> </page-layout>
</template> </template>
<script setup> <script lang="js" setup>
import { ref } from 'vue'; import { ref } from 'vue';
import { indexProps, useIndex } from '../../composables/useInertiaApiIndex.js'; import { indexProps, useIndex } from '../../composables/useInertiaApiIndex.js';
const props = defineProps(indexProps); const props = defineProps(indexProps);

View File

@ -74,7 +74,7 @@
</page-layout> </page-layout>
</template> </template>
<script setup> <script lang="js" setup>
import { ref, inject, defineProps } from 'vue'; import { ref, inject, defineProps } from 'vue';
import { useIndex } from '../../composables/useIndex.js'; import { useIndex } from '../../composables/useIndex.js';

View File

@ -77,7 +77,7 @@
</page-layout> </page-layout>
</template> </template>
<script setup> <script lang="js" setup>
import { indexProps, useIndex } from '../../composables/useInertiaApiIndex.js'; import { indexProps, useIndex } from '../../composables/useInertiaApiIndex.js';
import SettingLayout from '../setting/Layout.vue'; import SettingLayout from '../setting/Layout.vue';

View File

@ -2,17 +2,14 @@
<div class="sidebar flex flex-col group is-bright"> <div class="sidebar flex flex-col group is-bright">
<page-header title="Ausbildungen" @close="$emit('close')"> <page-header title="Ausbildungen" @close="$emit('close')">
<template #toolbar> <template #toolbar>
<page-toolbar-button v-if="single === null" color="primary" icon="plus" @click.prevent="create">Neue <page-toolbar-button v-if="single === null" color="primary" icon="plus" @click.prevent="create">Neue Ausbildung</page-toolbar-button>
Ausbildung</page-toolbar-button> <page-toolbar-button v-if="single !== null" color="primary" icon="undo" @click.prevent="cancel">Zurück</page-toolbar-button>
<page-toolbar-button v-if="single !== null" color="primary" icon="undo"
@click.prevent="cancel">Zurück</page-toolbar-button>
</template> </template>
</page-header> </page-header>
<form v-if="single" class="p-6 grid gap-4 justify-start" @submit.prevent="submit"> <form v-if="single" class="p-6 grid gap-4 justify-start" @submit.prevent="submit">
<f-text id="completed_at" v-model="single.completed_at" type="date" label="Datum" required></f-text> <f-text id="completed_at" v-model="single.completed_at" type="date" label="Datum" required></f-text>
<f-select id="course_id" v-model="single.course_id" name="course_id" :options="meta.courses" label="Baustein" <f-select id="course_id" v-model="single.course_id" name="course_id" :options="meta.courses" label="Baustein" required></f-select>
required></f-select>
<f-text id="event_name" v-model="single.event_name" label="Veranstaltung" required></f-text> <f-text id="event_name" v-model="single.event_name" label="Veranstaltung" required></f-text>
<f-text id="organizer" v-model="single.organizer" label="Veranstalter" required></f-text> <f-text id="organizer" v-model="single.organizer" label="Veranstalter" required></f-text>
<button type="submit" class="btn btn-primary">Absenden</button> <button type="submit" class="btn btn-primary">Absenden</button>
@ -34,10 +31,8 @@
<td v-text="course.organizer"></td> <td v-text="course.organizer"></td>
<td v-text="course.completed_at_human"></td> <td v-text="course.completed_at_human"></td>
<td class="flex"> <td class="flex">
<a href="#" class="inline-flex btn btn-warning btn-sm" @click.prevent="edit(course)"><ui-sprite <a href="#" class="inline-flex btn btn-warning btn-sm" @click.prevent="edit(course)"><ui-sprite src="pencil"></ui-sprite></a>
src="pencil"></ui-sprite></a> <a href="#" class="inline-flex btn btn-danger btn-sm" @click.prevent="remove(course)"><ui-sprite src="trash"></ui-sprite></a>
<a href="#" class="inline-flex btn btn-danger btn-sm" @click.prevent="remove(course)"><ui-sprite
src="trash"></ui-sprite></a>
</td> </td>
</tr> </tr>
</table> </table>
@ -45,7 +40,7 @@
</div> </div>
</template> </template>
<script setup> <script lang="js" setup>
defineEmits(['close']); defineEmits(['close']);
import { useApiIndex } from '../../composables/useApiIndex.js'; import { useApiIndex } from '../../composables/useApiIndex.js';

View File

@ -18,7 +18,7 @@
</div> </div>
</template> </template>
<script setup> <script lang="js" setup>
defineEmits(['close']); defineEmits(['close']);
import { useApiIndex } from '../../composables/useApiIndex.js'; import { useApiIndex } from '../../composables/useApiIndex.js';

View File

@ -1,25 +1,25 @@
<template> <template>
<page-header title="Mitgliedschaften" @close="$emit('close')"> <page-header title="Mitgliedschaften" @close="$emit('close')">
<template #toolbar> <template #toolbar>
<page-toolbar-button v-if="single === null" color="primary" icon="plus" @click.prevent="create">Neue <page-toolbar-button v-if="single === null" color="primary" icon="plus" @click.prevent="create">Neue Mitgliedschaft</page-toolbar-button>
Mitgliedschaft</page-toolbar-button> <page-toolbar-button v-if="single !== null" color="primary" icon="undo" @click.prevent="single = null">Zurück</page-toolbar-button>
<page-toolbar-button v-if="single !== null" color="primary" icon="undo"
@click.prevent="single = null">Zurück</page-toolbar-button>
</template> </template>
</page-header> </page-header>
<form v-if="single" class="p-6 grid gap-4 justify-start" @submit.prevent="submit"> <form v-if="single" class="p-6 grid gap-4 justify-start" @submit.prevent="submit">
<f-select id="group_id" v-model="single.group_id" name="group_id" :options="meta.groups" label="Gruppierung" <f-select id="group_id" v-model="single.group_id" name="group_id" :options="meta.groups" label="Gruppierung" required></f-select>
required></f-select> <f-select id="activity_id" v-model="single.activity_id" name="activity_id" :options="meta.activities" label="Tätigkeit" required></f-select>
<f-select id="activity_id" v-model="single.activity_id" name="activity_id" :options="meta.activities" <f-select
label="Tätigkeit" required></f-select> v-if="single.activity_id"
<f-select v-if="single.activity_id" id="subactivity_id" :model-value="single.subactivity_id" name="subactivity_id" id="subactivity_id"
:options="meta.subactivities[single.activity_id]" label="Untertätigkeit" :model-value="single.subactivity_id"
@update:modelValue="setSubactivityId(single, $event)"></f-select> name="subactivity_id"
<f-switch v-if="displayPromisedAt" id="has_promise" :model-value="single.promised_at !== null" :options="meta.subactivities[single.activity_id]"
label="Hat Versprechen" @update:modelValue="setPromisedAtSwitch(single, $event)"></f-switch> label="Untertätigkeit"
<f-text v-show="displayPromisedAt && single.promised_at !== null" id="promised_at" v-model="single.promised_at" @update:modelValue="setSubactivityId(single, $event)"
type="date" label="Versprechensdatum" size="sm"></f-text> ></f-select>
<f-switch v-if="displayPromisedAt" id="has_promise" :model-value="single.promised_at !== null" label="Hat Versprechen" @update:modelValue="setPromisedAtSwitch(single, $event)"></f-switch>
<f-text v-show="displayPromisedAt && single.promised_at !== null" id="promised_at" v-model="single.promised_at" type="date" label="Versprechensdatum" size="sm"></f-text>
<button type="submit" class="btn btn-primary">Absenden</button> <button type="submit" class="btn btn-primary">Absenden</button>
</form> </form>
@ -39,17 +39,15 @@
<td v-text="membership.human_date"></td> <td v-text="membership.human_date"></td>
<td><ui-boolean-display :value="membership.is_active" dark></ui-boolean-display></td> <td><ui-boolean-display :value="membership.is_active" dark></ui-boolean-display></td>
<td class="flex space-x-1"> <td class="flex space-x-1">
<a href="#" class="inline-flex btn btn-warning btn-sm" @click.prevent="edit(membership)"><ui-sprite <a href="#" class="inline-flex btn btn-warning btn-sm" @click.prevent="edit(membership)"><ui-sprite src="pencil"></ui-sprite></a>
src="pencil"></ui-sprite></a> <a href="#" class="inline-flex btn btn-danger btn-sm" @click.prevent="remove(membership)"><ui-sprite src="trash"></ui-sprite></a>
<a href="#" class="inline-flex btn btn-danger btn-sm" @click.prevent="remove(membership)"><ui-sprite
src="trash"></ui-sprite></a>
</td> </td>
</tr> </tr>
</table> </table>
</div> </div>
</template> </template>
<script setup> <script lang="js" setup>
import { computed } from 'vue'; import { computed } from 'vue';
import dayjs from 'dayjs'; import dayjs from 'dayjs';

View File

@ -132,7 +132,7 @@
</page-layout> </page-layout>
</template> </template>
<script setup> <script lang="js" setup>
import MemberInvoicePositions from './MemberInvoicePositions.vue'; import MemberInvoicePositions from './MemberInvoicePositions.vue';
import MemberMemberships from './MemberMemberships.vue'; import MemberMemberships from './MemberMemberships.vue';
import MemberCourses from './MemberCourses.vue'; import MemberCourses from './MemberCourses.vue';

View File

@ -1,23 +1,20 @@
<template> <template>
<div class="flex space-x-1"> <div class="flex space-x-1">
<i-link v-tooltip="`Details`" :href="member.links.show" class="inline-flex btn btn-primary btn-sm"><ui-sprite <i-link v-tooltip="`Details`" :href="member.links.show" class="inline-flex btn btn-primary btn-sm"><ui-sprite src="eye"></ui-sprite></i-link>
src="eye"></ui-sprite></i-link> <i-link v-tooltip="`Bearbeiten`" :href="member.links.edit" class="inline-flex btn btn-warning btn-sm"><ui-sprite src="pencil"></ui-sprite></i-link>
<i-link v-tooltip="`Bearbeiten`" :href="member.links.edit" class="inline-flex btn btn-warning btn-sm"><ui-sprite <a v-show="hasModule('bill')" v-tooltip="`Zahlungen`" href="#" class="inline-flex btn btn-info btn-sm" @click.prevent="$emit('sidebar', 'invoicePosition')"
src="pencil"></ui-sprite></i-link> ><ui-sprite src="money"></ui-sprite
<a v-show="hasModule('bill')" v-tooltip="`Zahlungen`" href="#" class="inline-flex btn btn-info btn-sm" ></a>
@click.prevent="$emit('sidebar', 'invoicePosition')"><ui-sprite src="money"></ui-sprite></a> <a v-show="hasModule('courses')" v-tooltip="`Ausbildungen`" href="#" class="inline-flex btn btn-info btn-sm" @click.prevent="$emit('sidebar', 'courses')"
<a v-show="hasModule('courses')" v-tooltip="`Ausbildungen`" href="#" class="inline-flex btn btn-info btn-sm" ><ui-sprite src="course"></ui-sprite
@click.prevent="$emit('sidebar', 'courses')"><ui-sprite src="course"></ui-sprite></a> ></a>
<a v-tooltip="`Mitgliedschaften`" href="#" class="inline-flex btn btn-info btn-sm" <a v-tooltip="`Mitgliedschaften`" href="#" class="inline-flex btn btn-info btn-sm" @click.prevent="$emit('sidebar', 'membership')"><ui-sprite src="user"></ui-sprite></a>
@click.prevent="$emit('sidebar', 'membership')"><ui-sprite src="user"></ui-sprite></a> <a v-show="member.efz_link" v-tooltip="`EFZ Formular`" :href="member.efz_link" class="inline-flex btn btn-info btn-sm"><ui-sprite src="report"></ui-sprite></a>
<a v-show="member.efz_link" v-tooltip="`EFZ Formular`" :href="member.efz_link" <a v-tooltip="`Entfernen`" href="#" class="inline-flex btn btn-danger btn-sm" @click.prevent="$emit('remove')"><ui-sprite src="trash"></ui-sprite></a>
class="inline-flex btn btn-info btn-sm"><ui-sprite src="report"></ui-sprite></a>
<a v-tooltip="`Entfernen`" href="#" class="inline-flex btn btn-danger btn-sm"
@click.prevent="$emit('remove')"><ui-sprite src="trash"></ui-sprite></a>
</div> </div>
</template> </template>
<script setup> <script lang="js" setup>
defineProps({ defineProps({
member: { member: {
type: Object, type: Object,