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

(image error) 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

(image error) 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

(image error) Size: 372 B

View File

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

View File

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

View File

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

View File

@ -1,17 +1,23 @@
<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 anlegen</page-toolbar-button> <page-toolbar-button :href="meta.links.create" color="primary" icon="plus">Mitglied
<page-toolbar-button v-if="hasModule('bill')" :href="meta.links.allpayment" color="primary" icon="invoice">Rechnungen erstellen</page-toolbar-button> anlegen</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 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> </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 entfernt.</p> <p class="mt-2">Alle Zuordnungen (Ausbildungen, Rechnungen, Zahlungen, Tätigkeiten) werden ebenfalls
<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> 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"> <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> </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>
@ -20,45 +26,22 @@
</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" @update:model-value="setFilter('search', $event)"></f-text> <f-text id="search" :model-value="getFilter('search')" name="search" label="Suchen …" size="sm"
<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> @update:model-value="setFilter('search', $event)"></f-text>
<f-multipleselect <f-switch v-show="hasModule('bill')" id="ausstand" :model-value="getFilter('ausstand')" label="Nur Ausstände"
id="group_ids" size="sm" @update:model-value="setFilter('ausstand', $event)"></f-switch>
:options="meta.groups" <f-multipleselect id="group_ids" :options="meta.groups" :model-value="getFilter('group_ids')"
:model-value="getFilter('group_ids')" label="Gruppierungen" size="sm" name="group_ids"
label="Gruppierungen" @update:model-value="setFilter('group_ids', $event)"></f-multipleselect>
size="sm" <f-select v-show="hasModule('bill')" id="billKinds" name="billKinds" :options="meta.billKinds"
name="group_ids" :model-value="getFilter('bill_kind')" label="Rechnung" size="sm"
@update:model-value="setFilter('group_ids', $event)" @update:model-value="setFilter('bill_kind', $event)"></f-select>
></f-multipleselect> <f-multipleselect id="activity_ids" :options="meta.filterActivities" :model-value="getFilter('activity_ids')"
<f-select label="Tätigkeiten" size="sm" name="activity_ids"
v-show="hasModule('bill')" @update:model-value="setFilter('activity_ids', $event)"></f-multipleselect>
id="billKinds" <f-multipleselect id="subactivity_ids" :options="meta.filterSubactivities"
name="billKinds" :model-value="getFilter('subactivity_ids')" label="Untertätigkeiten" size="sm" name="subactivity_ids"
:options="meta.billKinds" @update:model-value="setFilter('subactivity_ids', $event)"></f-multipleselect>
: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>
@ -107,11 +90,14 @@
<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" fallback=""></ui-label> <ui-label v-show="hasModule('bill')" class="text-gray-100 block" :value="member.pending_payment"
fallback=""></ui-label>
</div> </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"> <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> </div>
</ui-box> </ui-box>
</div> </div>
@ -120,22 +106,13 @@
<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 <member-payments v-if="single !== null && sidebar === 'payment.index'" :subscriptions="meta.subscriptions"
v-if="single !== null && sidebar === 'payment.index'" :statuses="meta.statuses" :value="data[single]" @close="closeSidebar"></member-payments>
:subscriptions="meta.subscriptions" <member-memberships v-if="single !== null && sidebar === 'membership.index'" :groups="meta.groups"
:statuses="meta.statuses" :activities="meta.formActivities" :subactivities="meta.formSubactivities" :value="data[single]"
:value="data[single]" @close="closeSidebar"></member-memberships>
@close="closeSidebar" <member-courses v-if="single !== null && sidebar === 'courses.index'" :courses="meta.courses" :value="data[single]"
></member-payments> @close="closeSidebar"></member-courses>
<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> </page-layout>
</template> </template>
@ -145,15 +122,15 @@ 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 sidebar = ref(null); const sidebar = ref(null);
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}`);
@ -161,7 +138,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}`);