Add loading for memberships sidebar

This commit is contained in:
Philipp Lang 2023-10-13 13:07:16 +02:00
parent 450e715acd
commit bfc4663ba4
8 changed files with 217 additions and 143 deletions

View File

@ -7,6 +7,5 @@
@import 'layout'; @import 'layout';
@import 'buttons'; @import 'buttons';
@import 'table'; @import 'table';
@import 'sidebar';
@import 'bool'; @import 'bool';
@import 'form'; @import 'form';

View File

@ -1,3 +0,0 @@
.sidebar {
@apply fixed w-max shadow-2xl bg-gray-600 right-0 top-0 h-full;
}

View File

@ -6,7 +6,7 @@
<slot name="toolbar"></slot> <slot name="toolbar"></slot>
</div> </div>
<div class="flex items-center space-x-2 ml-2"> <div class="flex items-center space-x-2 ml-2">
<a href="#" v-if="$attrs.onClose" @click.prevent="$emit('close')" class="btn label btn-primary-light icon"> <a v-if="$attrs.onClose" href="#" class="btn label btn-primary-light icon" @click.prevent="$emit('close')">
<ui-sprite class="w-3 h-3" src="close"></ui-sprite> <ui-sprite class="w-3 h-3" src="close"></ui-sprite>
</a> </a>
<slot name="right"></slot> <slot name="right"></slot>
@ -14,14 +14,13 @@
</div> </div>
</template> </template>
<script> <script setup>
export default { defineProps({
props: { title: {
title: { type: String,
default: function () { default: function () {
return ''; return '';
},
}, },
}, },
}; });
</script> </script>

View File

@ -0,0 +1,20 @@
<template>
<div class="fixed w-full w-[80vw] max-w-[30rem] shadow-2xl bg-gray-600 right-0 top-0 h-full flex flex-col group is-bright">
<suspense>
<slot></slot>
<template #fallback>
<div class="flex flex-col h-full">
<page-header title="Lade …" @close="$emit('close')"> </page-header>
<div class="grow flex items-center justify-center">
<ui-spinner class="border-primary-400 w-32 h-32"></ui-spinner>
</div>
</div>
</template>
</suspense>
</div>
</template>
<script setup>
defineEmits(['close']);
</script>

77
resources/js/composables/useApiIndex.js vendored Normal file
View File

@ -0,0 +1,77 @@
import {ref, inject, onBeforeUnmount} from 'vue';
import {router} from '@inertiajs/vue3';
import useQueueEvents from './useQueueEvents.js';
export function useApiIndex(url, siteName) {
const axios = inject('axios');
const {startListener, stopListener} = useQueueEvents(siteName, () => reload());
const single = ref(null);
const inner = {
data: ref([]),
meta: ref({}),
};
async function reload(resetPage = true) {
var params = {
page: resetPage ? 1 : inner.meta.value.current_page,
};
var response = (await axios.post(url, params)).data;
inner.data.value = response.data;
inner.meta.value = response.meta;
}
function create() {
single.value = JSON.parse(JSON.stringify(inner.meta.value.default));
}
function edit(model) {
single.value = JSON.parse(JSON.stringify(model));
}
async function submit() {
single.value.id ? await axios.patch(single.value.links.update, single.value) : await axios.post(inner.meta.value.links.store, single.value);
await reload();
single.value = null;
}
async function remove(model) {
await axios.delete(model.links.destroy);
await reload();
}
function can(permission) {
return inner.meta.value.can[permission];
}
function requestCallback(successMessage, failureMessage) {
return {
onSuccess: () => {
this.$success(successMessage);
reload(false);
},
onFailure: () => {
this.$error(failureMessage);
reload(false);
},
preserveState: true,
};
}
startListener();
onBeforeUnmount(() => stopListener());
return {
data: inner.data,
meta: inner.meta,
single,
create,
edit,
reload,
can,
requestCallback,
router,
submit,
remove,
};
}

View File

@ -1,94 +1,62 @@
<template> <template>
<div class="sidebar flex flex-col group is-bright"> <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 Mitgliedschaft</page-toolbar-button>
<page-toolbar-button v-if="single === null" color="primary" icon="plus" @click.prevent="create">Neue 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="data.meta.groups" label="Gruppierung" required></f-select> <f-select id="group_id" v-model="single.group_id" name="group_id" :options="meta.groups" label="Gruppierung" required></f-select>
<f-select id="activity_id" v-model="single.activity_id" name="activity_id" :options="data.meta.activities" label="Tätigkeit" 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 <f-select
v-if="single.activity_id" v-if="single.activity_id"
id="subactivity_id" id="subactivity_id"
v-model="single.subactivity_id" v-model="single.subactivity_id"
name="subactivity_id" name="subactivity_id"
:options="data.meta.subactivities[single.activity_id]" :options="meta.subactivities[single.activity_id]"
label="Untertätigkeit" label="Untertätigkeit"
></f-select> ></f-select>
<f-switch id="has_promise" :model-value="single.promised_at !== null" label="Hat Versprechen" @update:modelValue="single.promised_at = $event ? '2000-02-02' : null"></f-switch> <f-switch id="has_promise" :model-value="single.promised_at !== null" label="Hat Versprechen" @update:modelValue="single.promised_at = $event ? '2000-02-02' : null"></f-switch>
<f-text v-show="single.promised_at !== null" id="promised_at" v-model="single.promised_at" type="date" label="Versprechensdatum" size="sm"></f-text> <f-text v-show="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>
<div v-else class="grow"> <div v-else class="grow">
<table class="custom-table custom-table-light custom-table-sm text-sm"> <table class="custom-table custom-table-light custom-table-sm text-sm">
<thead> <thead>
<th>Tätigkeit</th> <th>Tätigkeit</th>
<th>Untertätigkeit</th> <th>Untertätigkeit</th>
<th>Datum</th> <th>Datum</th>
<th>Aktiv</th> <th>Aktiv</th>
<th></th> <th></th>
</thead> </thead>
<tr v-for="(membership, index) in data.data" :key="index"> <tr v-for="(membership, index) in data" :key="index">
<td v-text="membership.activity_name"></td> <td v-text="membership.activity_name"></td>
<td v-text="membership.subactivity_name"></td> <td v-text="membership.subactivity_name"></td>
<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 src="pencil"></ui-sprite></a> <a href="#" class="inline-flex btn btn-warning btn-sm" @click.prevent="edit(membership)"><ui-sprite 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> </div>
</template> </template>
<script setup> <script setup>
import {ref, inject, onBeforeMount} from 'vue'; defineEmits(['close']);
const axios = inject('axios'); import {useApiIndex} from '../../composables/useApiIndex.js';
const props = defineProps({ const props = defineProps({
value: { url: {
type: Object, type: String,
required: true, required: true,
}, },
}); });
const {data, meta, reload, single, create, edit, submit, remove} = useApiIndex(props.url, 'membership');
const single = ref(null); await reload();
const data = ref({
meta: {},
data: [],
});
async function reload() {
data.value = (await axios.post(props.value.links.membership_index)).data;
}
onBeforeMount(async () => {
await reload();
});
function create() {
single.value = JSON.parse(JSON.stringify(data.value.meta.default));
}
function edit(membership) {
single.value = JSON.parse(JSON.stringify(membership));
}
async function submit() {
single.value.id ? await axios.patch(single.value.links.update, single.value) : await axios.post(data.value.meta.links.store, single.value);
await reload();
single.value = null;
}
async function remove(membership) {
await axios.delete(membership.links.destroy);
await reload();
}
</script> </script>

View File

@ -1,23 +1,17 @@
<template> <template>
<page-layout page-class="pb-6"> <page-layout page-class="pb-6">
<template #toolbar> <template #toolbar>
<page-toolbar-button :href="meta.links.create" color="primary" icon="plus">Mitglied <page-toolbar-button :href="meta.links.create" color="primary" icon="plus">Mitglied anlegen</page-toolbar-button>
anlegen</page-toolbar-button> <page-toolbar-button v-if="hasModule('bill')" :href="meta.links.allpayment" color="primary" icon="invoice">Rechnungen erstellen</page-toolbar-button>
<page-toolbar-button v-if="hasModule('bill')" :href="meta.links.allpayment" color="primary" <page-toolbar-button v-if="hasModule('bill')" :href="meta.links.sendpayment" color="info" icon="envelope">Rechnungen versenden</page-toolbar-button>
icon="invoice">Rechnungen erstellen</page-toolbar-button>
<page-toolbar-button v-if="hasModule('bill')" :href="meta.links.sendpayment" color="info"
icon="envelope">Rechnungen versenden</page-toolbar-button>
</template> </template>
<ui-popup v-if="deleting !== null" heading="Mitglied löschen?" @close="deleting.reject()"> <ui-popup v-if="deleting !== null" heading="Mitglied löschen?" @close="deleting.reject()">
<div> <div>
<p class="mt-4">Das Mitglied "{{ deleting.member.fullname }}" löschen?</p> <p class="mt-4">Das Mitglied "{{ deleting.member.fullname }}" löschen?</p>
<p class="mt-2">Alle Zuordnungen (Ausbildungen, Rechnungen, Zahlungen, Tätigkeiten) werden ebenfalls <p class="mt-2">Alle Zuordnungen (Ausbildungen, Rechnungen, Zahlungen, Tätigkeiten) werden ebenfalls entfernt.</p>
entfernt.</p> <ui-note v-if="!deleting.member.has_nami" class="mt-5" type="warning"> Dieses Mitglied ist nicht in NaMi vorhanden und wird daher nur in der AdReMa gelöscht werden. </ui-note>
<ui-note v-if="!deleting.member.has_nami" class="mt-5" type="warning"> Dieses Mitglied ist nicht in NaMi
vorhanden und wird daher nur in der AdReMa gelöscht werden. </ui-note>
<ui-note v-if="deleting.member.has_nami" class="mt-5" type="danger"> <ui-note v-if="deleting.member.has_nami" class="mt-5" type="danger">
Dieses Mitglied ist in NaMi vorhanden und wird daher in NaMi abgemeldet werden. Sofern Dieses Mitglied ist in NaMi vorhanden und wird daher in NaMi abgemeldet werden. Sofern "Datenweiterverwendung" eingeschaltet ist, wird das Mitglied auf inaktiv gesetzt.
"Datenweiterverwendung" eingeschaltet ist, wird das Mitglied auf inaktiv gesetzt.
</ui-note> </ui-note>
<div class="grid grid-cols-2 gap-3 mt-6"> <div class="grid grid-cols-2 gap-3 mt-6">
<a href="#" class="text-center btn btn-danger" @click.prevent="deleting.resolve">Mitglied loschen</a> <a href="#" class="text-center btn btn-danger" @click.prevent="deleting.resolve">Mitglied loschen</a>
@ -26,22 +20,45 @@
</div> </div>
</ui-popup> </ui-popup>
<page-filter breakpoint="xl"> <page-filter breakpoint="xl">
<f-text id="search" :model-value="getFilter('search')" name="search" label="Suchen …" size="sm" <f-text id="search" :model-value="getFilter('search')" name="search" label="Suchen …" size="sm" @update:model-value="setFilter('search', $event)"></f-text>
@update:model-value="setFilter('search', $event)"></f-text> <f-switch v-show="hasModule('bill')" id="ausstand" :model-value="getFilter('ausstand')" label="Nur Ausstände" size="sm" @update:model-value="setFilter('ausstand', $event)"></f-switch>
<f-switch v-show="hasModule('bill')" id="ausstand" :model-value="getFilter('ausstand')" label="Nur Ausstände" <f-multipleselect
size="sm" @update:model-value="setFilter('ausstand', $event)"></f-switch> id="group_ids"
<f-multipleselect id="group_ids" :options="meta.groups" :model-value="getFilter('group_ids')" :options="meta.groups"
label="Gruppierungen" size="sm" name="group_ids" :model-value="getFilter('group_ids')"
@update:model-value="setFilter('group_ids', $event)"></f-multipleselect> label="Gruppierungen"
<f-select v-show="hasModule('bill')" id="billKinds" name="billKinds" :options="meta.billKinds" size="sm"
:model-value="getFilter('bill_kind')" label="Rechnung" size="sm" name="group_ids"
@update:model-value="setFilter('bill_kind', $event)"></f-select> @update:model-value="setFilter('group_ids', $event)"
<f-multipleselect id="activity_ids" :options="meta.filterActivities" :model-value="getFilter('activity_ids')" ></f-multipleselect>
label="Tätigkeiten" size="sm" name="activity_ids" <f-select
@update:model-value="setFilter('activity_ids', $event)"></f-multipleselect> v-show="hasModule('bill')"
<f-multipleselect id="subactivity_ids" :options="meta.filterSubactivities" id="billKinds"
:model-value="getFilter('subactivity_ids')" label="Untertätigkeiten" size="sm" name="subactivity_ids" name="billKinds"
@update:model-value="setFilter('subactivity_ids', $event)"></f-multipleselect> :options="meta.billKinds"
:model-value="getFilter('bill_kind')"
label="Rechnung"
size="sm"
@update:model-value="setFilter('bill_kind', $event)"
></f-select>
<f-multipleselect
id="activity_ids"
:options="meta.filterActivities"
:model-value="getFilter('activity_ids')"
label="Tätigkeiten"
size="sm"
name="activity_ids"
@update:model-value="setFilter('activity_ids', $event)"
></f-multipleselect>
<f-multipleselect
id="subactivity_ids"
:options="meta.filterSubactivities"
:model-value="getFilter('subactivity_ids')"
label="Untertätigkeiten"
size="sm"
name="subactivity_ids"
@update:model-value="setFilter('subactivity_ids', $event)"
></f-multipleselect>
<button class="btn btn-primary label mr-2" @click.prevent="exportMembers"> <button class="btn btn-primary label mr-2" @click.prevent="exportMembers">
<ui-sprite class="w-3 h-3 xl:mr-2" src="save"></ui-sprite> <ui-sprite class="w-3 h-3 xl:mr-2" src="save"></ui-sprite>
<span class="hidden xl:inline">Exportieren</span> <span class="hidden xl:inline">Exportieren</span>
@ -90,14 +107,11 @@
<div class="text-xs text-gray-200" v-text="member.full_address"></div> <div class="text-xs text-gray-200" v-text="member.full_address"></div>
<div class="flex items-center mt-1 space-x-4"> <div class="flex items-center mt-1 space-x-4">
<tags :member="member"></tags> <tags :member="member"></tags>
<ui-label v-show="hasModule('bill')" class="text-gray-100 block" :value="member.pending_payment" <ui-label v-show="hasModule('bill')" class="text-gray-100 block" :value="member.pending_payment" fallback=""></ui-label>
fallback=""></ui-label>
</div> </div>
<actions class="mt-2" :member="member" @sidebar="openSidebar($event, member)" @remove="remove(member)"> <actions class="mt-2" :member="member" @sidebar="openSidebar($event, member)" @remove="remove(member)"> </actions>
</actions>
<div class="absolute right-0 top-0 h-full flex items-center mr-2"> <div class="absolute right-0 top-0 h-full flex items-center mr-2">
<i-link v-tooltip="`Details`" :href="member.links.show"><ui-sprite src="chevron" <i-link v-tooltip="`Details`" :href="member.links.show"><ui-sprite src="chevron" class="w-6 h-6 text-teal-100 -rotate-90"></ui-sprite></i-link>
class="w-6 h-6 text-teal-100 -rotate-90"></ui-sprite></i-link>
</div> </div>
</ui-box> </ui-box>
</div> </div>
@ -106,12 +120,11 @@
<ui-pagination class="mt-4" :value="meta" :only="['data']"></ui-pagination> <ui-pagination class="mt-4" :value="meta" :only="['data']"></ui-pagination>
</div> </div>
<member-payments v-if="single !== null && single.type === 'payment'" :subscriptions="meta.subscriptions" <ui-sidebar v-if="single !== null" @close="closeSidebar">
:statuses="meta.statuses" :value="single.model" @close="closeSidebar"></member-payments> <member-payments v-if="single.type === 'payment'" :subscriptions="meta.subscriptions" :statuses="meta.statuses" :value="single.model" @close="closeSidebar"></member-payments>
<member-memberships v-if="single !== null && single.type === 'membership'" :activities="meta.formActivities" <member-memberships v-if="single.type === 'membership'" :url="single.model.links.membership_index" @close="closeSidebar"></member-memberships>
:subactivities="meta.formSubactivities" :value="single.model" @close="closeSidebar"></member-memberships> <member-courses v-if="single.type === 'courses'" :courses="meta.courses" :value="single.model" @close="closeSidebar"></member-courses>
<member-courses v-if="single !== null && single.type === 'courses'" :courses="meta.courses" :value="single.model" </ui-sidebar>
@close="closeSidebar"></member-courses>
</page-layout> </page-layout>
</template> </template>
@ -121,14 +134,14 @@ import MemberMemberships from './MemberMemberships.vue';
import MemberCourses from './MemberCourses.vue'; import MemberCourses from './MemberCourses.vue';
import Tags from './Tags.vue'; import Tags from './Tags.vue';
import Actions from './index/Actions.vue'; import Actions from './index/Actions.vue';
import { indexProps, useIndex } from '../../composables/useIndex.js'; import {indexProps, useIndex} from '../../composables/useIndex.js';
import { ref, defineProps } from 'vue'; import {ref, defineProps} from 'vue';
const single = ref(null); const single = ref(null);
const deleting = ref(null); const deleting = ref(null);
const props = defineProps(indexProps); const props = defineProps(indexProps);
var { router, data, meta, getFilter, setFilter, filterString } = useIndex(props.data, 'member'); var {router, data, meta, getFilter, setFilter, filterString} = useIndex(props.data, 'member');
function exportMembers() { function exportMembers() {
window.open(`/member-export?filter=${filterString.value}`); window.open(`/member-export?filter=${filterString.value}`);
@ -136,7 +149,7 @@ function exportMembers() {
async function remove(member) { async function remove(member) {
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
deleting.value = { resolve, reject, member }; deleting.value = {resolve, reject, member};
}) })
.then(() => { .then(() => {
router.delete(`/member/${member.id}`); router.delete(`/member/${member.id}`);

View File

@ -1,7 +1,7 @@
<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 src="eye"></ui-sprite></i-link> <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>
<i-link v-tooltip="`Bearbeiten`" :href="`/member/${member.id}/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 src="pencil"></ui-sprite></i-link>
<a v-show="hasModule('bill')" v-tooltip="`Zahlungen`" href="#" class="inline-flex btn btn-info btn-sm" @click.prevent="$emit('sidebar', 'payment')"><ui-sprite src="money"></ui-sprite></a> <a v-show="hasModule('bill')" v-tooltip="`Zahlungen`" href="#" class="inline-flex btn btn-info btn-sm" @click.prevent="$emit('sidebar', 'payment')"><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" @click.prevent="$emit('sidebar', 'courses')"
><ui-sprite src="course"></ui-sprite ><ui-sprite src="course"></ui-sprite
@ -12,10 +12,11 @@
</div> </div>
</template> </template>
<script> <script setup>
export default { defineProps({
props: { member: {
member: {}, type: Object,
}, required: true,
}; }
});
</script> </script>