Add responsive layout

This commit is contained in:
philipp lang 2022-11-22 01:55:57 +01:00
parent a2b379e349
commit fec30c9fa8
13 changed files with 416 additions and 184 deletions

1
resources/img/svg/menu.svg Executable file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M0 3h20v2H0V3zm0 6h20v2H0V9zm0 6h20v2H0v-2z"/></svg>

After

Width:  |  Height:  |  Size: 121 B

View File

@ -4,7 +4,11 @@
<!-- ******************************** Sidebar ******************************** --> <!-- ******************************** Sidebar ******************************** -->
<div <div
class="fixed bg-gray-800 p-6 w-56 left-0 top-0 h-screen border-r border-gray-600 border-solid flex flex-col justify-between" class="fixed bg-gray-800 p-6 w-56 left-0 top-0 h-screen border-r border-gray-600 border-solid flex flex-col justify-between transition-all"
:class="{
'-left-[14rem]': !(menuVisible || (!menuVisible && menuOverflowVisible)),
'left-0': menuVisible || (!menuVisible && menuOverflowVisible),
}"
> >
<div class="grid gap-2"> <div class="grid gap-2">
<v-link href="/" menu="dashboard" icon="loss">Dashboard</v-link> <v-link href="/" menu="dashboard" icon="loss">Dashboard</v-link>
@ -18,12 +22,29 @@
<v-link href="/setting" menu="setting" icon="setting">Einstellungen</v-link> <v-link href="/setting" menu="setting" icon="setting">Einstellungen</v-link>
<v-link @click.prevent="$inertia.post('/logout')" icon="logout" href="/logout">Abmelden</v-link> <v-link @click.prevent="$inertia.post('/logout')" icon="logout" href="/logout">Abmelden</v-link>
</div> </div>
<a
href="#"
@click.prevent="menuOverflowVisible = false"
v-if="menuOverflowVisible && !menuVisible"
class="absolute right-0 top-0 mr-2 mt-2"
>
<svg-sprite src="close" class="w-5 h-5 text-gray-300"></svg-sprite>
</a>
</div> </div>
<div class="grow ml-56 bg-gray-900 flex flex-col"> <div
class="grow bg-gray-900 flex flex-col transition-all"
:class="{'ml-56': menuVisible, 'ml-0': !menuVisible}"
>
<div class="h-16 px-6 flex justify-between items-center border-b border-gray-600"> <div class="h-16 px-6 flex justify-between items-center border-b border-gray-600">
<div class="flex"> <div class="flex space-x-3 items-center">
<span class="text-xl font-semibold text-white leading-none" v-html="$page.props.title"></span> <a href="#" @click.prevent="menuOverflowVisible = !menuOverflowVisible" class="lg:hidden">
<svg-sprite src="menu" class="text-gray-100 w-4 h-4"></svg-sprite>
</a>
<span
class="text-sm md:text-xl font-semibold text-white leading-none"
v-html="$page.props.title"
></span>
<div class="flex ml-4"> <div class="flex ml-4">
<i-link <i-link
v-for="(link, index) in filterMenu" v-for="(link, index) in filterMenu"
@ -37,7 +58,7 @@
</i-link> </i-link>
</div> </div>
</div> </div>
<label for="search"> <label for="search" class="hidden md:block">
<input <input
class="shadow-lg bg-gray-800 rounded-lg py-2 px-3 text-gray-300 hover:bg-gray-700 focus:bg-gray-700 placeholder-gray-400" class="shadow-lg bg-gray-800 rounded-lg py-2 px-3 text-gray-300 hover:bg-gray-700 focus:bg-gray-700 placeholder-gray-400"
placeholder="Suchen…" placeholder="Suchen…"
@ -60,6 +81,12 @@ import {debounce} from 'lodash';
import mergesQueryString from '../mixins/mergesQueryString.js'; import mergesQueryString from '../mixins/mergesQueryString.js';
export default { export default {
data: function () {
return {
menuVisible: true,
menuOverflowVisible: false,
};
},
components: { components: {
VNotification: () => import('../components/VNotification.vue'), VNotification: () => import('../components/VNotification.vue'),
VLink, VLink,
@ -82,6 +109,27 @@ export default {
return this.$page.props.toolbar ? this.$page.props.toolbar.filter((menu) => menu.show !== false) : []; return this.$page.props.toolbar ? this.$page.props.toolbar.filter((menu) => menu.show !== false) : [];
}, },
}, },
methods: {
menuListener() {
var x = window.matchMedia('(min-width: 1024px)');
if (x.matches && !this.menuVisible) {
this.menuVisible = true;
this.menuOverflowVisible = false;
return;
}
if (!x.matches && this.menuVisible) {
this.menuVisible = false;
this.menuOverflowVisible = false;
return;
}
},
},
created() {
window.addEventListener('resize', this.menuListener);
this.menuListener();
},
}; };
</script> </script>

View File

@ -1,7 +1,7 @@
<template> <template>
<section class="bg-gray-800 p-3 rounded-lg flex flex-col"> <section class="bg-gray-800 p-3 rounded-lg flex flex-col">
<heading class="col-span-full">{{ heading }}</heading> <heading class="col-span-full" v-if="heading">{{ heading }}</heading>
<main :class="containerClass" class="mt-3"> <main :class="{'mt-3': heading, 'containerClass': true}">
<slot></slot> <slot></slot>
</main> </main>
</section> </section>
@ -11,7 +11,6 @@
export default { export default {
props: { props: {
heading: { heading: {
required: true,
type: String, type: String,
}, },
containerClass: { containerClass: {

View File

@ -1,182 +1,45 @@
<template> <template>
<div class="p-6 grid gap-6 this-grid grow"> <div class="p-6 grid gap-6 this-grid grow">
<!-- ****************************** Stammdaten ******************************* --> <box heading="Stammdaten" class="area-stamm hidden 2xl:block">
<box container-class="grid grid-cols-2 gap-3" heading="Stammdaten" class="area-stamm"> <stamm :inner="inner"></stamm>
<key-value class="col-span-2" label="Name" :value="inner.fullname"></key-value> </box>
<key-value class="col-span-2" label="Adresse" :value="inner.full_address"></key-value> <box heading="Kontakt" class="area-kontakt hidden 2xl:block">
<key-value label="Geburtsdatum" :value="inner.birthday_human"></key-value> <kontakt :inner="inner"></kontakt>
<key-value label="Alter" :value="inner.age"></key-value> </box>
<key-value label="Bundesland" :value="inner.region.name"></key-value> <box class="area-stammkontakt block 2xl:hidden">
<key-value label="Nationalität" :value="inner.nationality.name"></key-value> <tabs v-model="tabs.stammkontakt">
<key-value <stamm v-show="tabs.stammkontakt.active === 'stamm'" :inner="inner"></stamm>
v-show="inner.other_country" <kontakt v-show="tabs.stammkontakt.active === 'kontakt'" :inner="inner"></kontakt>
label="Andere Staatsangehörigkeit" </tabs>
:value="inner.other_country"
></key-value>
</box> </box>
<!-- ******************************** Kontakt ******************************** --> <box container-class="" heading="Prävention" class="area-praev hidden 2xl:block">
<box container-class="grid gap-3" heading="Kontakt" class="area-kontakt"> <prae :inner="inner"></prae>
<key-value </box>
v-show="inner.main_phone" <box heading="System" class="area-system hidden 2xl:block">
label="Telefon Eltern" <system :inner="inner"></system>
:value="inner.main_phone" </box>
type="tel" <box class="area-praesystem block 2xl:hidden">
></key-value> <tabs v-model="tabs.praesystem">
<key-value <prae v-show="tabs.praesystem.active === 'prae'" :inner="inner"></prae>
v-show="inner.mobile_phone" <system v-show="tabs.praesystem.active === 'system'" :inner="inner"></system>
label="Handy Eltern" </tabs>
:value="inner.mobile_phone"
type="tel"
></key-value>
<key-value
v-show="inner.work_phone"
label="Telefon Eltern geschäftlich"
:value="inner.work_phone"
type="tel"
></key-value>
<key-value
v-show="inner.children_phone"
label="Telefon Kind"
:value="inner.children_phone"
type="tel"
></key-value>
<key-value v-show="inner.email" label="E-Mail-Adresse Kind" :value="inner.email" type="email"></key-value>
<key-value
v-show="inner.email_parents"
label="E-Mail-Adresse Eltern"
:value="inner.email_parents"
type="email"
></key-value>
<key-value v-show="inner.fax" label="Fax" :value="inner.fax" type="tel"></key-value>
</box> </box>
<!-- ****************************** Prävention ******************************* --> <box heading="Ausbildungen" class="area-courses hidden 2xl:block">
<box container-class="flex gap-3" heading="Prävention" class="area-praev"> <courses :inner="inner"></courses>
<div class="grid gap-3">
<key-value
class="col-start-1"
label="Führungszeugnis eingesehen"
:value="inner.efz_human ? inner.efz_human : 'nie'"
></key-value>
<key-value
class="col-start-1"
label="Präventionsschulung"
:value="inner.ps_at_human ? inner.ps_at_human : 'nie'"
></key-value>
<key-value
class="col-start-1"
label="Vertiefungsschulung"
:value="inner.more_ps_at_human ? inner.more_ps_at_human : 'nie'"
></key-value>
<key-value
class="col-start-1"
label="Einsatz ohne Schulung"
:value="inner.without_education_at_human ? inner.without_education_at_human : 'nie'"
></key-value>
<key-value
class="col-start-1"
label="Einsatz ohne EFZ"
:value="inner.without_efz_at_human ? inner.without_efz_at_human : 'nie'"
></key-value>
</div>
<div class="grid gap-3 content-start">
<boolean :value="inner.has_vk" long-label="Verhaltenskodex unterschrieben" label="VK"></boolean>
<boolean :value="inner.has_svk" long-label="SVK unterschrieben" label="SVK"></boolean>
<boolean
:value="inner.multiply_pv"
long-label="Multiplikator*in Präventionsschulung"
label="Multipl. PS"
></boolean>
<boolean
:value="inner.multiply_more_pv"
long-label="Multiplikator*in Vertierungsschulung"
label="Multipl. VS"
></boolean>
</div>
</box> </box>
<!-- ******************************** Courses ******************************** --> <box heading="Mitgliedschaften" class="area-memberships hidden 2xl:block">
<box heading="Ausbildungen" class="area-courses"> <memberships :inner="inner"></memberships>
<table
cellspacing="0"
cellpadding="0"
border="0"
class="custom-table custom-table-sm text-sm"
v-if="inner.courses.length"
>
<thead>
<th>Datum</th>
<th>Baustein</th>
<th>Veranstaltung</th>
<th>Organisator</th>
</thead>
<tr v-for="(course, index) in inner.courses" :key="index">
<td v-text="course.completed_at_human"></td>
<td v-text="course.course.short_name"></td>
<td v-text="course.event_name"></td>
<td v-text="course.organizer"></td>
</tr>
</table>
<div class="py-3 text-gray-400 text-center" v-else>Keine Ausbildungen vorhanden</div>
</box> </box>
<!-- ******************************** System ********************************* --> <box heading="Zahlungen" class="area-payments hidden 2xl:block">
<box container-class="grid gap-3" heading="System" class="area-system"> <payments :inner="inner"></payments>
<key-value v-show="inner.nami_id" label="Nami Mitgliedsnummer" :value="inner.nami_id"></key-value>
<key-value label="Beitrag" :value="inner.subscription ? inner.subscription.name : 'kein'"></key-value>
<key-value v-if="inner.joined_at_human" label="Eintrittsdatum" :value="inner.joined_at_human"></key-value>
<key-value v-if="inner.bill_kind_name" label="Rechnung" :value="inner.bill_kind_name"></key-value>
<boolean :value="inner.send_newspaper" label="Mittendrin versenden"></boolean>
</box> </box>
<!-- *************************** Mitgliedschaften **************************** --> <box heading="Karte" container-class="grow" class="area-map hidden 2xl:block">
<box heading="Mitgliedschaften" class="area-memberships"> <vmap :inner="inner"></vmap>
<table cellspacing="0" cellpadding="0" border="0" class="custom-table custom-table-sm text-sm">
<thead>
<th>Tätigkeit</th>
<th>Untertätigkeit</th>
<th>Datum</th>
</thead>
<tr v-for="(membership, index) in inner.memberships" :key="index">
<td v-text="membership.activity_name"></td>
<td v-text="membership.subactivity_name"></td>
<td v-text="membership.human_date"></td>
</tr>
</table>
</box>
<!-- ******************************* Zahlungen ******************************* -->
<box heading="Zahlungen" class="area-payments">
<table cellspacing="0" cellpadding="0" border="0" class="custom-table custom-table-sm text-sm">
<thead>
<th>Nr</th>
<th>Status</th>
<th>Betrag-Name</th>
<th>Betrag</th>
</thead>
<tr v-for="(payment, index) in inner.payments" :key="index">
<td v-text="payment.nr"></td>
<td v-text="payment.status_name"></td>
<td v-text="payment.subscription.name"></td>
<td v-text="payment.subscription.amount_human"></td>
</tr>
</table>
</box>
<!-- ********************************* Karte ********************************* -->
<box heading="Karte" container-class="grow" class="area-map">
<iframe
width="100%"
height="100%"
frameborder="0"
scrolling="no"
marginheight="0"
marginwidth="0"
src="https://www.openstreetmap.org/export/embed.html?bbox=9.699318408966066%2C47.484177893725764%2C9.729595184326174%2C47.49977861091604&amp;layer=mapnik&amp;marker=47.49197883161885%2C9.714467525482178"
style="border: 1px solid black"
>
</iframe>
</box> </box>
</div> </div>
</template> </template>
@ -186,6 +49,22 @@ export default {
data: function () { data: function () {
return { return {
inner: {}, inner: {},
tabs: {
stammkontakt: {
children: {
stamm: 'Stammdaten',
kontakt: 'Kontakt',
},
active: 'stamm',
},
praesystem: {
children: {
prae: 'Prävention',
system: 'System',
},
active: 'system',
},
},
}; };
}, },
@ -196,9 +75,16 @@ export default {
}, },
components: { components: {
'key-value': () => import('./KeyValue'), box: () => import('./Box'),
'boolean': () => import('./Boolean'), stamm: () => import('./boxes/Stamm'),
'box': () => import('./Box'), kontakt: () => import('./boxes/Kontakt'),
prae: () => import('./boxes/Prae'),
courses: () => import('./boxes/Courses'),
system: () => import('./boxes/System'),
payments: () => import('./boxes/Payments'),
memberships: () => import('./boxes/Memberships'),
vmap: () => import('./boxes/Vmap'),
tabs: () => import('./Tabs'),
}, },
created() { created() {
@ -210,19 +96,27 @@ export default {
<style scoped> <style scoped>
.this-grid { .this-grid {
grid-template-areas: grid-template-areas:
'stamm kontakt prae system' 'stammkontakt'
'praesystem';
grid-template-columns: 1fr;
}
@media screen and (min-width: 1536px) {
.this-grid {
grid-template-areas:
'stamm kontakt praev system'
'courses courses memberships memberships' 'courses courses memberships memberships'
'payments payments map map'; 'payments payments map map';
grid-template-columns: max-content max-content max-content 1fr; grid-template-columns: max-content max-content max-content 1fr;
} }
}
.area-stamm { .area-stamm {
grid-area: stamm; grid-area: stamm;
} }
.area-kontakt { .area-kontakt {
grid-area: kontakt; grid-area: kontakt;
} }
.area-prae { .area-praev {
grid-area: prae; grid-area: praev;
} }
.area-courses { .area-courses {
grid-area: courses; grid-area: courses;
@ -239,4 +133,7 @@ export default {
.area-map { .area-map {
grid-area: map; grid-area: map;
} }
.area-stammkontakt {
grid-area: stammkontakt;
}
</style> </style>

View File

@ -0,0 +1,46 @@
<template>
<section>
<div class="flex space-x-2 border-b border-teal-200">
<a
v-for="(v, index) in inner.children"
href="#"
@click.prevent="navigate(index)"
class="text-teal-800 font-semibold hover:text-teal-600 transition-all"
:class="{'text-teal-600': inner.active === index}"
>
<span v-text="v"></span>
</a>
</div>
<div class="mt-3">
<slot></slot>
</div>
</section>
</template>
<script>
export default {
data: function () {
return {
inner: {
children: {},
active: null,
},
};
},
props: {
value: {},
},
methods: {
navigate(v) {
this.inner.active = v;
this.$emit('input', this.inner);
},
},
created() {
this.inner = this.value;
},
};
</script>

View File

@ -0,0 +1,33 @@
<template>
<div>
<table
cellspacing="0"
cellpadding="0"
border="0"
class="custom-table overflow-auto custom-table-sm text-sm"
v-if="inner.courses.length"
>
<thead>
<th>Datum</th>
<th>Baustein</th>
<th>Veranstaltung</th>
<th>Organisator</th>
</thead>
<tr v-for="(course, index) in inner.courses" :key="index">
<td v-text="course.completed_at_human"></td>
<td v-text="course.course.short_name"></td>
<td v-text="course.event_name"></td>
<td v-text="course.organizer"></td>
</tr>
</table>
<div class="py-3 text-gray-400 text-center" v-else>Keine Ausbildungen vorhanden</div>
</div>
</template>
<script>
export default {
props: {
inner: {},
},
};
</script>

View File

@ -0,0 +1,37 @@
<template>
<div class="grid gap-3">
<key-value v-show="inner.main_phone" label="Telefon Eltern" :value="inner.main_phone" type="tel"></key-value>
<key-value v-show="inner.mobile_phone" label="Handy Eltern" :value="inner.mobile_phone" type="tel"></key-value>
<key-value
v-show="inner.work_phone"
label="Telefon Eltern geschäftlich"
:value="inner.work_phone"
type="tel"
></key-value>
<key-value
v-show="inner.children_phone"
label="Telefon Kind"
:value="inner.children_phone"
type="tel"
></key-value>
<key-value v-show="inner.email" label="E-Mail-Adresse Kind" :value="inner.email" type="email"></key-value>
<key-value
v-show="inner.email_parents"
label="E-Mail-Adresse Eltern"
:value="inner.email_parents"
type="email"
></key-value>
<key-value v-show="inner.fax" label="Fax" :value="inner.fax" type="tel"></key-value>
</div>
</template>
<script>
export default {
props: {
inner: {},
},
components: {
'key-value': () => import('../KeyValue'),
},
};
</script>

View File

@ -0,0 +1,24 @@
<template>
<div>
<table cellspacing="0" cellpadding="0" border="0" class="custom-table overflow-auto custom-table-sm text-sm">
<thead>
<th>Tätigkeit</th>
<th>Untertätigkeit</th>
<th>Datum</th>
</thead>
<tr v-for="(membership, index) in inner.memberships" :key="index">
<td v-text="membership.activity_name"></td>
<td v-text="membership.subactivity_name"></td>
<td v-text="membership.human_date"></td>
</tr>
</table>
</div>
</template>
<script>
export default {
props: {
inner: {},
},
};
</script>

View File

@ -0,0 +1,22 @@
<template>
<table cellspacing="0" cellpadding="0" border="0" class="custom-table custom-table-sm text-sm">
<thead>
<th>Tätigkeit</th>
<th>Untertätigkeit</th>
<th>Datum</th>
</thead>
<tr v-for="(membership, index) in inner.memberships" :key="index">
<td v-text="membership.activity_name"></td>
<td v-text="membership.subactivity_name"></td>
<td v-text="membership.human_date"></td>
</tr>
</table>
</template>
<script>
export default {
props: {
inner: {},
},
};
</script>

View File

@ -0,0 +1,57 @@
<template>
<div class="flex gap-3">
<div class="grid gap-3">
<key-value
class="col-start-1"
label="Führungszeugnis eingesehen"
:value="inner.efz_human ? inner.efz_human : 'nie'"
></key-value>
<key-value
class="col-start-1"
label="Präventionsschulung"
:value="inner.ps_at_human ? inner.ps_at_human : 'nie'"
></key-value>
<key-value
class="col-start-1"
label="Vertiefungsschulung"
:value="inner.more_ps_at_human ? inner.more_ps_at_human : 'nie'"
></key-value>
<key-value
class="col-start-1"
label="Einsatz ohne Schulung"
:value="inner.without_education_at_human ? inner.without_education_at_human : 'nie'"
></key-value>
<key-value
class="col-start-1"
label="Einsatz ohne EFZ"
:value="inner.without_efz_at_human ? inner.without_efz_at_human : 'nie'"
></key-value>
</div>
<div class="grid gap-3 content-start">
<boolean :value="inner.has_vk" long-label="Verhaltenskodex unterschrieben" label="VK"></boolean>
<boolean :value="inner.has_svk" long-label="SVK unterschrieben" label="SVK"></boolean>
<boolean
:value="inner.multiply_pv"
long-label="Multiplikator*in Präventionsschulung"
label="Multipl. PS"
></boolean>
<boolean
:value="inner.multiply_more_pv"
long-label="Multiplikator*in Vertierungsschulung"
label="Multipl. VS"
></boolean>
</div>
</div>
</template>
<script>
export default {
props: {
inner: {},
},
components: {
'key-value': () => import('../KeyValue'),
'boolean': () => import('../Boolean'),
},
};
</script>

View File

@ -0,0 +1,26 @@
<template>
<div class="grid grid-cols-2 gap-3">
<key-value class="col-span-2" label="Name" :value="inner.fullname"></key-value>
<key-value class="col-span-2" label="Adresse" :value="inner.full_address"></key-value>
<key-value label="Geburtsdatum" :value="inner.birthday_human"></key-value>
<key-value label="Alter" :value="inner.age"></key-value>
<key-value label="Bundesland" :value="inner.region.name"></key-value>
<key-value label="Nationalität" :value="inner.nationality.name"></key-value>
<key-value
v-show="inner.other_country"
label="Andere Staatsangehörigkeit"
:value="inner.other_country"
></key-value>
</div>
</template>
<script>
export default {
props: {
inner: {},
},
components: {
'key-value': () => import('../KeyValue'),
},
};
</script>

View File

@ -0,0 +1,21 @@
<template>
<div class="grid gap-3">
<key-value v-show="inner.nami_id" label="Nami Mitgliedsnummer" :value="inner.nami_id"></key-value>
<key-value label="Beitrag" :value="inner.subscription ? inner.subscription.name : 'kein'"></key-value>
<key-value v-if="inner.joined_at_human" label="Eintrittsdatum" :value="inner.joined_at_human"></key-value>
<key-value v-if="inner.bill_kind_name" label="Rechnung" :value="inner.bill_kind_name"></key-value>
<boolean :value="inner.send_newspaper" label="Mittendrin versenden"></boolean>
</div>
</template>
<script>
export default {
props: {
inner: {},
},
components: {
'key-value': () => import('../KeyValue'),
'boolean': () => import('../Boolean'),
},
};
</script>

View File

@ -0,0 +1,21 @@
<template>
<iframe
class="grow"
width="100%"
height="100%"
frameborder="0"
scrolling="no"
marginheight="0"
marginwidth="0"
src="https://www.openstreetmap.org/export/embed.html?bbox=9.699318408966066%2C47.484177893725764%2C9.729595184326174%2C47.49977861091604&amp;layer=mapnik&amp;marker=47.49197883161885%2C9.714467525482178"
>
</iframe>
</template>
<script>
export default {
props: {
inner: {},
},
};
</script>