Remodel for Mobile
This commit is contained in:
@@ -9,7 +9,7 @@ const api = useNuxtApp().$api
|
||||
|
||||
|
||||
const type = route.params.type
|
||||
const platform = await useCapacitor().getIsPhone() ? "mobile" : "default"
|
||||
const platform = await useCapacitor().getIsNative() ? "mobile" : "default"
|
||||
|
||||
|
||||
const dataType = dataStore.dataTypes[route.params.type]
|
||||
|
||||
@@ -3,9 +3,12 @@ import {useTempStore} from "~/stores/temp.js";
|
||||
import FloatingActionButton from "~/components/mobile/FloatingActionButton.vue";
|
||||
import EntityTable from "~/components/EntityTable.vue";
|
||||
import EntityTableMobile from "~/components/EntityTableMobile.vue";
|
||||
import {setPageLayout} from "#app";
|
||||
|
||||
const { has } = usePermission()
|
||||
|
||||
const platformIsNative = useCapacitor().getIsNative()
|
||||
|
||||
defineShortcuts({
|
||||
'/': () => {
|
||||
//console.log(searchinput)
|
||||
@@ -67,8 +70,31 @@ const sort = ref({
|
||||
|
||||
const columnsToFilter = ref({})
|
||||
|
||||
const showMobileFilter = ref(false)
|
||||
|
||||
|
||||
//Functions
|
||||
|
||||
function resetMobileFilters() {
|
||||
if (!itemsMeta.value?.distinctValues) return
|
||||
|
||||
Object.keys(itemsMeta.value.distinctValues).forEach(key => {
|
||||
columnsToFilter.value[key] = [...itemsMeta.value.distinctValues[key]]
|
||||
})
|
||||
|
||||
showMobileFilter.value = false
|
||||
setupPage()
|
||||
}
|
||||
|
||||
function applyMobileFilters() {
|
||||
Object.keys(columnsToFilter.value).forEach(key => {
|
||||
tempStore.modifyFilter(type, key, columnsToFilter.value[key])
|
||||
})
|
||||
|
||||
showMobileFilter.value = false
|
||||
setupPage()
|
||||
}
|
||||
|
||||
const clearSearchString = () => {
|
||||
tempStore.clearSearchString(type)
|
||||
searchString.value = ''
|
||||
@@ -77,13 +103,14 @@ const clearSearchString = () => {
|
||||
|
||||
const performSearch = async () => {
|
||||
tempStore.modifySearchString(type,searchString)
|
||||
changePage(1,true)
|
||||
setupPage()
|
||||
}
|
||||
|
||||
const changePage = (number) => {
|
||||
const changePage = (number, noSetup = false) => {
|
||||
page.value = number
|
||||
tempStore.modifyPages(type, number)
|
||||
setupPage()
|
||||
if(!noSetup) setupPage()
|
||||
}
|
||||
|
||||
|
||||
@@ -99,10 +126,23 @@ const changeSort = (column) => {
|
||||
changePage(1)
|
||||
}
|
||||
|
||||
const isFiltered = computed(() => {
|
||||
if (!itemsMeta.value?.distinctValues) return false
|
||||
|
||||
return Object.keys(columnsToFilter.value).some(key => {
|
||||
const allValues = itemsMeta.value.distinctValues[key]
|
||||
const selected = columnsToFilter.value[key]
|
||||
if (!allValues || !selected) return false
|
||||
return selected.length !== allValues.length
|
||||
})
|
||||
})
|
||||
|
||||
//SETUP
|
||||
|
||||
const setupPage = async () => {
|
||||
loading.value = true
|
||||
setPageLayout(platformIsNative ? "mobile" : "default")
|
||||
|
||||
|
||||
const filters = {
|
||||
archived:false
|
||||
@@ -160,17 +200,11 @@ const handleFilterChange = async (action,column) => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- <FloatingActionButton
|
||||
:label="`+ ${dataType.labelSingle}`"
|
||||
variant="outline"
|
||||
v-if="platform === 'mobile'"
|
||||
@click="router.push(`/standardEntity/${type}/create`)"
|
||||
/>-->
|
||||
<UDashboardNavbar :title="dataType.label" :badge="itemsMeta.total">
|
||||
<template #toggle>
|
||||
<div v-if="platform === 'mobile'"></div>
|
||||
<div v-if="platformIsNative"></div>
|
||||
</template>
|
||||
<template #right>
|
||||
<template #right v-if="!platformIsNative">
|
||||
<UTooltip :text="`${dataType.label} durchsuchen`">
|
||||
<UInput
|
||||
id="searchinput"
|
||||
@@ -211,58 +245,17 @@ const handleFilterChange = async (action,column) => {
|
||||
</UDashboardNavbar>
|
||||
|
||||
|
||||
<UDashboardToolbar>
|
||||
<UDashboardToolbar v-if="!platformIsNative">
|
||||
<template #left>
|
||||
<UTooltip :text="`${dataType.label} pro Seite`">
|
||||
<USelectMenu
|
||||
:options="[10,15,25,50,100,250]"
|
||||
:options="[{value:10},{value:15, disabled: itemsMeta.total < 15},{value:25, disabled: itemsMeta.total < 25},{value:50, disabled: itemsMeta.total < 50},{value:100, disabled: itemsMeta.total < 100},{value:250, disabled: itemsMeta.total < 250}]"
|
||||
v-model="pageLimit"
|
||||
value-attribute="value"
|
||||
option-attribute="value"
|
||||
@change="setupPage"
|
||||
/>
|
||||
</UTooltip>
|
||||
<!-- <UTooltip text="Erste Seite">
|
||||
<UButton
|
||||
variant="outline"
|
||||
@click="changePage(1)"
|
||||
icon="i-heroicons-chevron-double-left"
|
||||
:disabled="page <= 1"
|
||||
/>
|
||||
</UTooltip>
|
||||
<UTooltip text="Eine Seite nach vorne">
|
||||
<UButton
|
||||
variant="outline"
|
||||
@click="changePage(page-1)"
|
||||
icon="i-heroicons-chevron-left"
|
||||
:disabled="page <= 1"
|
||||
/>
|
||||
</UTooltip>
|
||||
<UTooltip
|
||||
v-for="pageNumber in itemsMeta.totalPages"
|
||||
:text="`Zu Seite ${pageNumber} wechseln`"
|
||||
>
|
||||
<UButton
|
||||
:variant="page === pageNumber ? 'solid' : 'outline'"
|
||||
@click="changePage(pageNumber)"
|
||||
>
|
||||
{{pageNumber}}
|
||||
</UButton>
|
||||
</UTooltip>
|
||||
<UTooltip text="Eine Seite nach hinten">
|
||||
<UButton
|
||||
variant="outline"
|
||||
@click="changePage(page+1)"
|
||||
icon="i-heroicons-chevron-right"
|
||||
:disabled="page >= itemsMeta.totalPages"
|
||||
/>
|
||||
</UTooltip>
|
||||
<UTooltip text="Letzte Seite">
|
||||
<UButton
|
||||
variant="outline"
|
||||
@click="changePage(itemsMeta.totalPages)"
|
||||
icon="i-heroicons-chevron-double-right"
|
||||
:disabled="page >= itemsMeta.totalPages"
|
||||
/>
|
||||
</UTooltip>-->
|
||||
<UPagination
|
||||
v-if="initialSetupDone && items.length > 0"
|
||||
:disabled="loading"
|
||||
@@ -299,95 +292,96 @@ const handleFilterChange = async (action,column) => {
|
||||
</template>
|
||||
</UDashboardToolbar>
|
||||
|
||||
<UTable
|
||||
:loading="loading"
|
||||
:loading-state="{ icon: 'i-heroicons-arrow-path-20-solid', label: 'Loading...' }"
|
||||
sort-mode="manual"
|
||||
v-model:sort="sort"
|
||||
@update:sort="setupPage"
|
||||
v-if="dataType && columns && items.length > 0 && !loading"
|
||||
:rows="items"
|
||||
:columns="columns"
|
||||
class="w-full"
|
||||
style="height: 85dvh"
|
||||
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"
|
||||
@select="(i) => router.push(`/standardEntity/${type}/show/${i.id}`) "
|
||||
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: `Keine ${dataType.label} anzuzeigen` }"
|
||||
>
|
||||
<template
|
||||
v-for="column in dataType.templateColumns.filter(i => !i.disabledInTable)"
|
||||
v-slot:[`${column.key}-header`]="{row}">
|
||||
<InputGroup>
|
||||
<UTooltip v-if="column.sortable">
|
||||
<div v-if="!platformIsNative">
|
||||
<UTable
|
||||
:loading="loading"
|
||||
:loading-state="{ icon: 'i-heroicons-arrow-path-20-solid', label: 'Loading...' }"
|
||||
sort-mode="manual"
|
||||
v-model:sort="sort"
|
||||
@update:sort="setupPage"
|
||||
v-if="dataType && columns && items.length > 0 && !loading"
|
||||
:rows="items"
|
||||
:columns="columns"
|
||||
class="w-full"
|
||||
style="height: 85dvh"
|
||||
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"
|
||||
@select="(i) => router.push(`/standardEntity/${type}/show/${i.id}`) "
|
||||
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: `Keine ${dataType.label} anzuzeigen` }"
|
||||
>
|
||||
<template
|
||||
v-for="column in dataType.templateColumns.filter(i => !i.disabledInTable)"
|
||||
v-slot:[`${column.key}-header`]="{row}">
|
||||
<InputGroup>
|
||||
<UTooltip v-if="column.sortable">
|
||||
<UButton
|
||||
variant="outline"
|
||||
@click="changeSort(column.key)"
|
||||
:color="sort.column === column.key ? 'primary' : 'white'"
|
||||
:icon="sort.column === column.key ? (sort.direction === 'asc' ? 'i-heroicons-arrow-up' : 'i-heroicons-arrow-down') : 'i-heroicons-arrows-up-down'"
|
||||
>
|
||||
|
||||
</UButton>
|
||||
</UTooltip>
|
||||
<UTooltip
|
||||
v-if="column.distinct"
|
||||
:text="!columnsToFilter[column.key]?.length > 0 ? `Keine Einträge für ${column.label} verfügbar` : `${column.label} Spalte nach Einträgen filtern`"
|
||||
>
|
||||
<USelectMenu
|
||||
:options="itemsMeta?.distinctValues?.[column.key]"
|
||||
v-model="columnsToFilter[column.key]"
|
||||
multiple
|
||||
@change="handleFilterChange('change', column.key)"
|
||||
searchable
|
||||
searchable-placeholder="Suche..."
|
||||
:search-attributes="[column.key]"
|
||||
:ui-menu="{ width: 'min-w-max' }"
|
||||
clear-search-on-close
|
||||
>
|
||||
|
||||
<template #empty>
|
||||
Keine Einträge in der Spalte {{column.label}}
|
||||
</template>
|
||||
<template #default="{open}">
|
||||
<UButton
|
||||
:disabled="!columnsToFilter[column.key]?.length > 0"
|
||||
:variant="columnsToFilter[column.key]?.length !== itemsMeta.distinctValues?.[column.key]?.length ? 'outline' : 'solid'"
|
||||
:color="columnsToFilter[column.key]?.length !== itemsMeta.distinctValues?.[column.key]?.length ? 'primary' : 'white'"
|
||||
>
|
||||
<span class="truncate">{{ column.label }}</span>
|
||||
|
||||
<UIcon name="i-heroicons-chevron-right-20-solid" class="w-5 h-5 transition-transform text-gray-400 dark:text-gray-500" :class="[open && 'transform rotate-90']" />
|
||||
</UButton>
|
||||
</template>
|
||||
|
||||
|
||||
</USelectMenu>
|
||||
</UTooltip>
|
||||
<UButton
|
||||
variant="outline"
|
||||
@click="changeSort(column.key)"
|
||||
:color="sort.column === column.key ? 'primary' : 'white'"
|
||||
:icon="sort.column === column.key ? (sort.direction === 'asc' ? 'i-heroicons-arrow-up' : 'i-heroicons-arrow-down') : 'i-heroicons-arrows-up-down'"
|
||||
variant="solid"
|
||||
color="white"
|
||||
v-else
|
||||
class="mr-2 truncate"
|
||||
>{{column.label}}</UButton>
|
||||
|
||||
|
||||
<UTooltip
|
||||
text="Filter zurücksetzen"
|
||||
v-if="columnsToFilter[column.key]?.length !== itemsMeta.distinctValues?.[column.key]?.length && column.distinct"
|
||||
>
|
||||
|
||||
</UButton>
|
||||
</UTooltip>
|
||||
<UTooltip
|
||||
v-if="column.distinct"
|
||||
:text="!columnsToFilter[column.key]?.length > 0 ? `Keine Einträge für ${column.label} verfügbar` : `${column.label} Spalte nach Einträgen filtern`"
|
||||
>
|
||||
<USelectMenu
|
||||
:options="itemsMeta?.distinctValues?.[column.key]"
|
||||
v-model="columnsToFilter[column.key]"
|
||||
multiple
|
||||
@change="handleFilterChange('change', column.key)"
|
||||
searchable
|
||||
searchable-placeholder="Suche..."
|
||||
:search-attributes="[column.key]"
|
||||
:ui-menu="{ width: 'min-w-max' }"
|
||||
clear-search-on-close
|
||||
>
|
||||
|
||||
<template #empty>
|
||||
Keine Einträge in der Spalte {{column.label}}
|
||||
</template>
|
||||
<template #default="{open}">
|
||||
<UButton
|
||||
:disabled="!columnsToFilter[column.key]?.length > 0"
|
||||
:variant="columnsToFilter[column.key]?.length !== itemsMeta.distinctValues?.[column.key]?.length ? 'outline' : 'solid'"
|
||||
:color="columnsToFilter[column.key]?.length !== itemsMeta.distinctValues?.[column.key]?.length ? 'primary' : 'white'"
|
||||
>
|
||||
<span class="truncate">{{ column.label }}</span>
|
||||
|
||||
<UIcon name="i-heroicons-chevron-right-20-solid" class="w-5 h-5 transition-transform text-gray-400 dark:text-gray-500" :class="[open && 'transform rotate-90']" />
|
||||
</UButton>
|
||||
</template>
|
||||
<UButton
|
||||
@click="handleFilterChange('reset',column.key)"
|
||||
variant="outline"
|
||||
color="rose"
|
||||
>
|
||||
X
|
||||
</UButton>
|
||||
</UTooltip>
|
||||
|
||||
|
||||
</USelectMenu>
|
||||
</UTooltip>
|
||||
<UButton
|
||||
variant="solid"
|
||||
color="white"
|
||||
v-else
|
||||
class="mr-2 truncate"
|
||||
>{{column.label}}</UButton>
|
||||
</InputGroup>
|
||||
|
||||
|
||||
<UTooltip
|
||||
text="Filter zurücksetzen"
|
||||
v-if="columnsToFilter[column.key]?.length !== itemsMeta.distinctValues?.[column.key]?.length && column.distinct"
|
||||
>
|
||||
<UButton
|
||||
@click="handleFilterChange('reset',column.key)"
|
||||
variant="outline"
|
||||
color="rose"
|
||||
>
|
||||
X
|
||||
</UButton>
|
||||
</UTooltip>
|
||||
|
||||
|
||||
</InputGroup>
|
||||
|
||||
</template>
|
||||
<template #name-data="{row}">
|
||||
</template>
|
||||
<template #name-data="{row}">
|
||||
<span
|
||||
v-if="row.id === items[selectedItem].id"
|
||||
class="text-primary-500 font-bold">
|
||||
@@ -396,61 +390,236 @@ const handleFilterChange = async (action,column) => {
|
||||
>
|
||||
{{dataType.templateColumns.find(i => i.key === "name").maxLength ? (row.name.length > dataType.templateColumns.find(i => i.key === "name").maxLength ? `${row.name.substring(0,dataType.templateColumns.find(i => i.key === "name").maxLength)}...` : row.name ) : row.name}}
|
||||
</UTooltip> </span>
|
||||
<span v-else>
|
||||
<span v-else>
|
||||
<UTooltip
|
||||
:text="row.name"
|
||||
>
|
||||
{{dataType.templateColumns.find(i => i.key === "name").maxLength ? (row.name.length > dataType.templateColumns.find(i => i.key === "name").maxLength ? `${row.name.substring(0,dataType.templateColumns.find(i => i.key === "name").maxLength)}...` : row.name ) : row.name}}
|
||||
</UTooltip>
|
||||
</span>
|
||||
</template>
|
||||
<template #fullName-data="{row}">
|
||||
</template>
|
||||
<template #fullName-data="{row}">
|
||||
<span
|
||||
v-if="row.id === items[selectedItem].id"
|
||||
class="text-primary-500 font-bold">{{row.fullName}}
|
||||
</span>
|
||||
<span v-else>
|
||||
<span v-else>
|
||||
{{row.fullName}}
|
||||
</span>
|
||||
</template>
|
||||
<template #licensePlate-data="{row}">
|
||||
</template>
|
||||
<template #licensePlate-data="{row}">
|
||||
<span
|
||||
v-if="row.id === items[selectedItem].id"
|
||||
class="text-primary-500 font-bold">{{row.licensePlate}}
|
||||
</span>
|
||||
<span v-else>
|
||||
<span v-else>
|
||||
{{row.licensePlate}}
|
||||
</span>
|
||||
</template>
|
||||
<template
|
||||
v-for="column in dataType.templateColumns.filter(i => i.key !== 'name' && i.key !== 'fullName' && i.key !== 'licensePlate' && !i.disabledInTable)"
|
||||
v-slot:[`${column.key}-data`]="{row}">
|
||||
<component v-if="column.component" :is="column.component" :row="row"></component>
|
||||
<span v-else-if="row[column.key]">
|
||||
</template>
|
||||
<template
|
||||
v-for="column in dataType.templateColumns.filter(i => i.key !== 'name' && i.key !== 'fullName' && i.key !== 'licensePlate' && !i.disabledInTable)"
|
||||
v-slot:[`${column.key}-data`]="{row}">
|
||||
<component v-if="column.component" :is="column.component" :row="row"></component>
|
||||
<span v-else-if="row[column.key]">
|
||||
<UTooltip :text="row[column.key]">
|
||||
{{row[column.key] ? `${column.maxLength ? (row[column.key].length > column.maxLength ? `${row[column.key].substring(0,column.maxLength)}...` : row[column.key]) : row[column.key]} ${column.unit ? column.unit : ''}`: ''}}
|
||||
</UTooltip>
|
||||
</span>
|
||||
|
||||
</template>
|
||||
</UTable>
|
||||
<UCard
|
||||
class="w-1/3 mx-auto mt-10"
|
||||
v-else-if="!loading"
|
||||
>
|
||||
<div
|
||||
class="flex flex-col text-center"
|
||||
</template>
|
||||
</UTable>
|
||||
<UCard
|
||||
class="w-1/3 mx-auto mt-10"
|
||||
v-else-if="!loading"
|
||||
>
|
||||
<UIcon
|
||||
class="mx-auto w-10 h-10 mb-5"
|
||||
name="i-heroicons-circle-stack-20-solid"/>
|
||||
<span class="font-bold">Keine {{dataType.label}} anzuzeigen</span>
|
||||
<div
|
||||
class="flex flex-col text-center"
|
||||
>
|
||||
<UIcon
|
||||
class="mx-auto w-10 h-10 mb-5"
|
||||
name="i-heroicons-circle-stack-20-solid"/>
|
||||
<span class="font-bold">Keine {{dataType.label}} anzuzeigen</span>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</UCard>
|
||||
<UProgress v-else animation="carousel" class="w-3/4 mx-auto mt-5"></UProgress>
|
||||
</div>
|
||||
<div v-else class="relative flex flex-col h-[calc(100dvh-80px)]">
|
||||
|
||||
<!-- Mobile Searchbar (sticky top) -->
|
||||
<div class="p-2 bg-white dark:bg-gray-900 border-b sticky top-0 z-20">
|
||||
<InputGroup>
|
||||
<UInput
|
||||
v-model="searchString"
|
||||
icon="i-heroicons-magnifying-glass"
|
||||
placeholder="Suche..."
|
||||
@keyup="performSearch"
|
||||
@change="performSearch"
|
||||
class="w-full"
|
||||
/>
|
||||
<UButton
|
||||
v-if="searchString.length > 0"
|
||||
icon="i-heroicons-x-mark"
|
||||
variant="ghost"
|
||||
color="rose"
|
||||
@click="clearSearchString()"
|
||||
|
||||
/>
|
||||
<UButton
|
||||
icon="i-heroicons-funnel"
|
||||
variant="ghost"
|
||||
:color="isFiltered ? 'primary' : 'gray'"
|
||||
@click="showMobileFilter = true"
|
||||
/>
|
||||
</InputGroup>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Scroll Area -->
|
||||
<UDashboardPanelContent class="flex-1 overflow-y-auto px-2 py-3 space-y-3 pb-[calc(8vh+env(safe-area-inset-bottom))] mobile-scroll-area">
|
||||
|
||||
</UCard>
|
||||
<UProgress v-else animation="carousel" class="w-3/4 mx-auto mt-5"></UProgress>
|
||||
<UCard
|
||||
v-for="item in items"
|
||||
:key="item.id"
|
||||
class="p-4 rounded-xl shadow-sm border cursor-pointer active:scale-[0.98] transition"
|
||||
@click="router.push(`/standardEntity/${type}/show/${item.id}`)"
|
||||
>
|
||||
<div class="flex items-center justify-between mb-1">
|
||||
<p class="text-base font-semibold truncate text-primary-600">
|
||||
{{
|
||||
dataType.templateColumns.find(i => i.title)?.key
|
||||
? item[dataType.templateColumns.find(i => i.title).key]
|
||||
: null
|
||||
}}
|
||||
</p>
|
||||
|
||||
<UIcon
|
||||
name="i-heroicons-chevron-right-20-solid"
|
||||
class="text-gray-400 w-5 h-5 flex-shrink-0"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p class="text-sm text-gray-500 truncate">
|
||||
{{ dataType.numberRangeHolder ? item[dataType.numberRangeHolder] : null }}
|
||||
</p>
|
||||
|
||||
<div
|
||||
v-for="secondInfo in dataType.templateColumns.filter(i => i.secondInfo)"
|
||||
:key="secondInfo.key"
|
||||
class="text-sm text-gray-400 truncate"
|
||||
>
|
||||
{{
|
||||
(secondInfo.secondInfoKey && item[secondInfo.key])
|
||||
? item[secondInfo.key][secondInfo.secondInfoKey]
|
||||
: item[secondInfo.key]
|
||||
}}
|
||||
</div>
|
||||
</UCard>
|
||||
|
||||
<div
|
||||
v-if="!loading && items.length > 0"
|
||||
class="p-4 bg-white dark:bg-gray-900 border-t flex items-center justify-center mt-4 rounded-xl"
|
||||
>
|
||||
<UPagination
|
||||
v-if="initialSetupDone && items.length > 0"
|
||||
:disabled="loading"
|
||||
v-model="page"
|
||||
:page-count="pageLimit"
|
||||
:total="itemsMeta.total"
|
||||
@update:modelValue="(i) => changePage(i)"
|
||||
show-first
|
||||
show-last
|
||||
:first-button="{ icon: 'i-heroicons-chevron-double-left', color: 'gray' }"
|
||||
:last-button="{ icon: 'i-heroicons-chevron-double-right', trailing: true, color: 'gray' }"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Empty -->
|
||||
<UCard
|
||||
v-if="!loading && items.length === 0"
|
||||
class="mx-auto mt-10 p-6 text-center"
|
||||
>
|
||||
<UIcon name="i-heroicons-circle-stack-20-solid" class="mx-auto w-10 h-10 mb-3"/>
|
||||
<p class="font-bold">Keine {{ dataType.label }} gefunden</p>
|
||||
</UCard>
|
||||
|
||||
<div v-if="loading" class="mt-5">
|
||||
<UProgress animation="carousel" class="w-3/4 mx-auto"></UProgress>
|
||||
</div>
|
||||
|
||||
</UDashboardPanelContent>
|
||||
<!-- Mobile Filter Slideover -->
|
||||
<USlideover
|
||||
v-model="showMobileFilter"
|
||||
side="bottom"
|
||||
:ui="{ width: '100%', height: 'auto', maxHeight: '90vh' }"
|
||||
class="pb-[env(safe-area-inset-bottom)]"
|
||||
>
|
||||
<!-- Header -->
|
||||
<div class="p-4 border-b flex items-center justify-between flex-shrink-0">
|
||||
<h2 class="text-xl font-bold">Filter</h2>
|
||||
<UButton
|
||||
icon="i-heroicons-x-mark"
|
||||
variant="ghost"
|
||||
@click="showMobileFilter = false"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Scrollable content -->
|
||||
<div class="flex-1 overflow-y-auto p-4 space-y-6">
|
||||
|
||||
<div
|
||||
v-for="column in dataType.templateColumns.filter(c => c.distinct)"
|
||||
:key="column.key"
|
||||
class="space-y-2"
|
||||
>
|
||||
<p class="font-semibold">{{ column.label }}</p>
|
||||
|
||||
<USelectMenu
|
||||
v-model="columnsToFilter[column.key]"
|
||||
:options="itemsMeta?.distinctValues?.[column.key]"
|
||||
multiple
|
||||
searchable
|
||||
:search-attributes="[column.key]"
|
||||
placeholder="Auswählen…"
|
||||
:ui-menu="{ width: '100%' }"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Footer FIXED in card -->
|
||||
<div
|
||||
class="
|
||||
flex justify-between gap-3
|
||||
px-4 py-4 border-t flex-shrink-0
|
||||
bg-white dark:bg-gray-900
|
||||
rounded-b-2xl
|
||||
"
|
||||
>
|
||||
<UButton
|
||||
color="rose"
|
||||
variant="outline"
|
||||
class="flex-1"
|
||||
@click="resetMobileFilters"
|
||||
>
|
||||
Zurücksetzen
|
||||
</UButton>
|
||||
|
||||
<UButton
|
||||
color="primary"
|
||||
class="flex-1"
|
||||
@click="applyMobileFilters"
|
||||
>
|
||||
Anwenden
|
||||
</UButton>
|
||||
</div>
|
||||
</USlideover>
|
||||
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
Reference in New Issue
Block a user