Enhance pagination

This commit is contained in:
Arwed Molitor 2023-07-27 14:00:27 +02:00 committed by Philipp Lang
parent cd22d41f28
commit 5e416e5ed7
7 changed files with 120 additions and 161 deletions

View File

@ -0,0 +1 @@
<svg height="393.982" viewBox="0 0 106.351 98.496" width="425.402" xmlns="http://www.w3.org/2000/svg"><path d="M.44 4.66a4.57 4.57 0 0 1 1.41-3.295c1.882-1.82 4.928-1.82 6.808 0l44.737 43.3 44.738-43.3c1.881-1.82 4.927-1.82 6.807 0a4.552 4.552 0 0 1 0 6.589L56.8 54.547c-1.881 1.82-4.927 1.82-6.807 0L1.85 7.954A4.57 4.57 0 0 1 .44 4.66Z" style="stroke-width:1.18403"/><path d="M0 47.242a4.57 4.57 0 0 1 1.41-3.294c1.882-1.82 4.928-1.82 6.808 0l44.737 43.3 44.738-43.3c1.881-1.82 4.927-1.82 6.807 0a4.552 4.552 0 0 1 0 6.589L56.36 97.13c-1.881 1.82-4.927 1.82-6.807 0L1.41 50.537A4.57 4.57 0 0 1 0 47.242Z" style="stroke-width:1.18403"/><path d="M.44 4.66a4.57 4.57 0 0 1 1.41-3.295c1.882-1.82 4.928-1.82 6.808 0l44.737 43.3 44.738-43.3c1.881-1.82 4.927-1.82 6.807 0a4.552 4.552 0 0 1 0 6.589L56.8 54.547c-1.881 1.82-4.927 1.82-6.807 0L1.85 7.954A4.57 4.57 0 0 1 .44 4.66Z" style="stroke-width:1.18403"/></svg>

After

Width:  |  Height:  |  Size: 910 B

View File

@ -1,36 +0,0 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 407.437 407.437" style="enable-background:new 0 0 407.437 407.437;" xml:space="preserve">
<polygon points="386.258,91.567 203.718,273.512 21.179,91.567 0,112.815 203.718,315.87 407.437,112.815 "/>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 619 B

View File

@ -0,0 +1 @@
<svg height="223.651" viewBox="0 0 105.911 55.913" width="423.642" xmlns="http://www.w3.org/2000/svg"><path d="M0 4.66a4.57 4.57 0 0 1 1.41-3.295c1.882-1.82 4.928-1.82 6.808 0l44.737 43.3 44.738-43.3c1.881-1.82 4.927-1.82 6.807 0a4.552 4.552 0 0 1 0 6.589L56.36 54.547c-1.881 1.82-4.927 1.82-6.807 0L1.41 7.954A4.57 4.57 0 0 1 0 4.66Z" style="stroke-width:1.18403"/></svg>

After

Width:  |  Height:  |  Size: 372 B

View File

@ -5,14 +5,13 @@
<span v-show="required" class="text-red-800">&nbsp;*</span>
</span>
<div class="relative real-field-wrap" :class="`size-${size}`">
<div
@click="visible = !visible"
class="flex items-center border-gray-600 text-gray-300 leading-none border-solid bg-gray-700 w-full appearance-none outline-none rounded-lg size-sm text-xs px-1 border pr-6"
v-text="`${modelValue.length} Einträge ausgewählt`"
></div>
<div v-show="visible" class="absolute w-[max-content] z-30 max-h-[31rem] overflow-auto shadow-lg bg-gray-600 border border-gray-500 rounded-lg p-2 top-7">
<div v-for="(option, index) in parsedOptions" class="flex items-center space-x-2" :key="index">
<f-switch :id="`${id}-${index}`" size="sm" :modelValue="modelValue.includes(option.id)" :value="option.id" @update:modelValue="trigger(option, $event)"></f-switch>
<div class="flex items-center border-gray-600 text-gray-300 leading-none border-solid bg-gray-700 w-full appearance-none outline-none rounded-lg size-sm text-xs px-1 border pr-6"
@click="visible = !visible" v-text="`${modelValue.length} Einträge ausgewählt`"></div>
<div v-show="visible"
class="absolute w-[max-content] z-30 max-h-[31rem] overflow-auto shadow-lg bg-gray-600 border border-gray-500 rounded-lg p-2 top-7">
<div v-for="(option, index) in parsedOptions" :key="index" class="flex items-center space-x-2">
<f-switch :id="`${id}-${index}`" size="sm" :model-value="modelValue.includes(option.id)"
:value="option.id" @update:modelValue="trigger(option, $event)"></f-switch>
<div class="text-sm text-gray-200" v-text="option.name"></div>
</div>
</div>
@ -21,11 +20,11 @@
<div v-if="hint" v-tooltip="hint">
<ui-sprite src="info-button" class="info-button"></ui-sprite>
</div>
<div class="px-1 relative" v-if="size != 'xs'">
<ui-sprite class="chevron w-3 h-3 fill-current" src="chevron-down"></ui-sprite>
<div v-if="size != 'xs'" class="px-1 relative">
<ui-sprite class="chevron w-3 h-3 fill-current" src="chevron"></ui-sprite>
</div>
<div class="px-1 relative" v-if="size == 'xs'">
<ui-sprite class="chevron w-2 h-2 fill-current" src="chevron-down"></ui-sprite>
<div v-if="size == 'xs'" class="px-1 relative">
<ui-sprite class="chevron w-2 h-2 fill-current" src="chevron"></ui-sprite>
</div>
</div>
</div>
@ -36,12 +35,6 @@
import map from 'lodash/map';
export default {
emits: ['update:modelValue'],
data: function () {
return {
visible: false,
};
},
props: {
disabled: {
type: Boolean,
@ -83,13 +76,19 @@ export default {
},
},
},
emits: ['update:modelValue'],
data: function () {
return {
visible: false,
};
},
computed: {
parsedOptions() {
return Array.isArray(this.options)
? this.options
: map(this.options, (value, key) => {
return {name: value, id: key};
});
return { name: value, id: key };
});
},
},
methods: {

View File

@ -14,11 +14,11 @@
<div v-if="hint" v-tooltip="hint">
<ui-sprite src="info-button" class="info-button"></ui-sprite>
</div>
<div class="px-1 relative" v-if="size != 'xs'">
<ui-sprite class="chevron w-3 h-3 fill-current" src="chevron-down"></ui-sprite>
<div v-if="size != 'xs'" class="px-1 relative">
<ui-sprite class="chevron w-3 h-3 fill-current" src="chevron"></ui-sprite>
</div>
<div class="px-1 relative" v-if="size == 'xs'">
<ui-sprite class="chevron w-2 h-2 fill-current" src="chevron-down"></ui-sprite>
<div v-if="size == 'xs'" class="px-1 relative">
<ui-sprite class="chevron w-2 h-2 fill-current" src="chevron"></ui-sprite>
</div>
</div>
</div>
@ -29,7 +29,6 @@
import map from 'lodash/map';
export default {
emits: ['update:modelValue'],
props: {
disabled: {
type: Boolean,
@ -80,21 +79,14 @@ export default {
},
},
},
emits: ['update:modelValue'],
computed: {
parsedOptions() {
return Array.isArray(this.options)
? this.options
: map(this.options, (value, key) => {
return {name: value, id: key};
});
},
},
methods: {
trigger(v) {
this.$emit('update:modelValue', /^[0-9]+$/.test(v.target.value) ? parseInt(v.target.value) : v.target.value ? v.target.value : null);
},
clear() {
this.$emit('update:modelValue', null);
return { name: value, id: key };
});
},
},
mounted() {
@ -107,6 +99,14 @@ export default {
this.$emit('update:modelValue', null);
}
},
methods: {
trigger(v) {
this.$emit('update:modelValue', /^[0-9]+$/.test(v.target.value) ? parseInt(v.target.value) : v.target.value ? v.target.value : null);
},
clear() {
this.$emit('update:modelValue', null);
},
},
};
</script>

View File

@ -1,18 +1,32 @@
<template>
<div class="justify-between flex items-baseline">
<div class="text-sm text-gray-500" v-html="desc"></div>
<div class="-mx-1 items-baseline" :class="{hidden: value.last_page == 1, flex: value.last_page > 1}">
<div class="pl-1 pr-3 text-gray-500 text-sm">Seite:</div>
<div v-for="(link, index) in links" :key="index" class="px-1">
<button
:key="index"
href="#"
class="rounded text-sm w-8 h-8 text-primary-100 flex items-center justify-center leading-none shadow"
:class="{'bg-primary-700': link.current, 'bg-primary-900': !link.current}"
@click.prevent="goto(link)"
v-text="link.page"
></button>
</div>
<div class="flex flex-col md:flex-row justify-between items-center space-y-3 md:space-y-0">
<div class="text-sm text-gray-500" v-text="desc"></div>
<div v-if="value.last_page > 1" class="items-center flex space-x-2">
<div class="hidden sm:flex text-gray-500 text-sm" v-text="pages"></div>
<button v-if="value.current_page !== 1" href="#"
class="rounded hidden sm:flex w-8 h-8 text-primary-100 items-center justify-center leading-none shadow bg-primary-900 hover:bg-primary-800 items-center justify-center"
@click.prevent="goto(1)">
<ui-sprite class="w-3 h-3 fill-current rotate-90" src="chevron-double"></ui-sprite>
</button>
<button v-if="value.current_page !== 1" href="#"
class="rounded !ml-0 sm:!ml-2 flex w-8 h-8 text-primary-100 items-center justify-center leading-none shadow bg-primary-900 hover:bg-primary-800 items-center justify-center"
@click.prevent="goto(value.current_page - 1)">
<ui-sprite class="w-3 h-3 fill-current rotate-90" src="chevron"></ui-sprite>
</button>
<button v-for="(button, index) in pageButtons" :key="index" href="#"
class="rounded text-sm w-8 h-8 text-primary-100 flex items-center justify-center leading-none shadow"
:class="{ 'bg-primary-700': button.current, 'bg-primary-900 hover:bg-primary-800': !button.current }"
@click.prevent="goto(button.page)" v-text="button.page"></button>
<button v-if="value.current_page !== value.last_page" href="#"
class="flex rounded text-sm w-8 h-8 text-primary-100 items-center justify-center leading-none shadow bg-primary-900 hover:bg-primary-800 items-center justify-center"
@click.prevent="goto(value.current_page + 1)">
<ui-sprite class="w-3 h-3 fill-current -rotate-90" src="chevron"></ui-sprite>
</button>
<button v-if="value.current_page !== value.last_page" href="#"
class="hidden sm:flex rounded text-sm w-8 h-8 text-primary-100 items-center justify-center leading-none shadow bg-primary-900 hover:bg-primary-800 items-center justify-center"
@click.prevent="goto(value.last_page)">
<ui-sprite class="w-3 h-3 fill-current -rotate-90" src="chevron-double"></ui-sprite>
</button>
</div>
</div>
</template>
@ -34,20 +48,23 @@ export default {
},
computed: {
links() {
var links = [];
pageButtons() {
var buttons = [];
var from = Math.max(1, this.value.current_page - 3);
var to = Math.min(this.value.last_page, this.value.current_page + 3);
var from = Math.max(1, this.value.current_page - 2);
var to = Math.min(this.value.last_page, this.value.current_page + 2);
for (var i = from; i <= to; i++) {
links.push({
buttons.push({
page: i,
current: i === this.value.current_page,
});
}
return links;
return buttons;
},
pages() {
return `Seite ${this.value.current_page} von ${this.value.last_page}`;
},
desc() {
return `${this.value.from} - ${this.value.to} von ${this.value.total} Einträgen`;
@ -56,12 +73,12 @@ export default {
methods: {
goto(page) {
if (this.$attrs.onReload) {
this.$emit('reload', page.page);
this.$emit('reload', page);
return;
}
var params = new URLSearchParams(window.location.search);
params.set('page', page.page);
params.set('page', page);
this.$inertia.visit(window.location.pathname + '?' + params.toString(), {
only: this.only,

View File

@ -1,17 +1,23 @@
<template>
<page-layout page-class="pb-6">
<template #toolbar>
<page-toolbar-button :href="meta.links.create" color="primary" icon="plus">Mitglied 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.sendpayment" color="info" icon="envelope">Rechnungen versenden</page-toolbar-button>
<page-toolbar-button :href="meta.links.create" color="primary" icon="plus">Mitglied
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.sendpayment" color="info"
icon="envelope">Rechnungen versenden</page-toolbar-button>
</template>
<ui-popup v-if="deleting !== null" heading="Mitglied löschen?" @close="deleting.reject()">
<div>
<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 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>
<p class="mt-2">Alle Zuordnungen (Ausbildungen, Rechnungen, Zahlungen, Tätigkeiten) werden ebenfalls
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="danger">
Dieses Mitglied ist in NaMi vorhanden und wird daher in NaMi abgemeldet werden. Sofern "Datenweiterverwendung" eingeschaltet ist, wird das Mitglied auf inaktiv gesetzt.
Dieses Mitglied ist in NaMi vorhanden und wird daher in NaMi abgemeldet werden. Sofern
"Datenweiterverwendung" eingeschaltet ist, wird das Mitglied auf inaktiv gesetzt.
</ui-note>
<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>
@ -20,45 +26,22 @@
</div>
</ui-popup>
<page-filter breakpoint="xl">
<f-text id="search" :model-value="getFilter('search')" name="search" label="Suchen …" size="sm" @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-multipleselect
id="group_ids"
:options="meta.groups"
:model-value="getFilter('group_ids')"
label="Gruppierungen"
size="sm"
name="group_ids"
@update:model-value="setFilter('group_ids', $event)"
></f-multipleselect>
<f-select
v-show="hasModule('bill')"
id="billKinds"
name="billKinds"
: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>
<f-text id="search" :model-value="getFilter('search')" name="search" label="Suchen …" size="sm"
@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-multipleselect id="group_ids" :options="meta.groups" :model-value="getFilter('group_ids')"
label="Gruppierungen" size="sm" name="group_ids"
@update:model-value="setFilter('group_ids', $event)"></f-multipleselect>
<f-select v-show="hasModule('bill')" id="billKinds" name="billKinds" :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">
<ui-sprite class="w-3 h-3 xl:mr-2" src="save"></ui-sprite>
<span class="hidden xl:inline">Exportieren</span>
@ -107,11 +90,14 @@
<div class="text-xs text-gray-200" v-text="member.full_address"></div>
<div class="flex items-center mt-1 space-x-4">
<tags :member="member"></tags>
<ui-label v-show="hasModule('bill')" class="text-gray-100 block" :value="member.pending_payment" fallback=""></ui-label>
<ui-label v-show="hasModule('bill')" class="text-gray-100 block" :value="member.pending_payment"
fallback=""></ui-label>
</div>
<actions class="mt-2" :member="member" @sidebar="openSidebar(index, $event)" @remove="remove(member)"> </actions>
<actions class="mt-2" :member="member" @sidebar="openSidebar(index, $event)" @remove="remove(member)">
</actions>
<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-down" class="w-6 h-6 text-teal-100 -rotate-90"></ui-sprite></i-link>
<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>
</div>
</ui-box>
</div>
@ -120,22 +106,13 @@
<ui-pagination class="mt-4" :value="meta" :only="['data']"></ui-pagination>
</div>
<member-payments
v-if="single !== null && sidebar === 'payment.index'"
:subscriptions="meta.subscriptions"
:statuses="meta.statuses"
:value="data[single]"
@close="closeSidebar"
></member-payments>
<member-memberships
v-if="single !== null && sidebar === 'membership.index'"
:groups="meta.groups"
:activities="meta.formActivities"
:subactivities="meta.formSubactivities"
:value="data[single]"
@close="closeSidebar"
></member-memberships>
<member-courses v-if="single !== null && sidebar === 'courses.index'" :courses="meta.courses" :value="data[single]" @close="closeSidebar"></member-courses>
<member-payments v-if="single !== null && sidebar === 'payment.index'" :subscriptions="meta.subscriptions"
:statuses="meta.statuses" :value="data[single]" @close="closeSidebar"></member-payments>
<member-memberships v-if="single !== null && sidebar === 'membership.index'" :groups="meta.groups"
:activities="meta.formActivities" :subactivities="meta.formSubactivities" :value="data[single]"
@close="closeSidebar"></member-memberships>
<member-courses v-if="single !== null && sidebar === 'courses.index'" :courses="meta.courses" :value="data[single]"
@close="closeSidebar"></member-courses>
</page-layout>
</template>
@ -145,15 +122,15 @@ import MemberMemberships from './MemberMemberships.vue';
import MemberCourses from './MemberCourses.vue';
import Tags from './Tags.vue';
import Actions from './index/Actions.vue';
import {indexProps, useIndex} from '../../composables/useIndex.js';
import {ref, defineProps} from 'vue';
import { indexProps, useIndex } from '../../composables/useIndex.js';
import { ref, defineProps } from 'vue';
const sidebar = ref(null);
const single = ref(null);
const deleting = ref(null);
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() {
window.open(`/member-export?filter=${filterString.value}`);
@ -161,7 +138,7 @@ function exportMembers() {
async function remove(member) {
new Promise((resolve, reject) => {
deleting.value = {resolve, reject, member};
deleting.value = { resolve, reject, member };
})
.then(() => {
router.delete(`/member/${member.id}`);