Remodel for Mobile

This commit is contained in:
2025-11-20 12:38:38 +01:00
parent 5f6df7c69d
commit 24f576aeaa
13 changed files with 1226 additions and 595 deletions

View File

@@ -1,70 +1,134 @@
<script setup>
const props = defineProps({
queryStringData: {
type: String
},
item: {
type: Object,
required: true
},
type: {
type: String,
required: true
},
topLevelType: {
type: String,
required: true
},
platform: {
type: String,
required: true
}
item: { type: Object, required: true },
type: { type: String, required: true },
topLevelType: { type: String, required: true },
platform: { type: String, required: true }
})
const emit = defineEmits(["updateNeeded"])
const files = useFiles()
const availableFiles = ref([])
const activeFile = ref(null)
const showViewer = ref(false)
const setup = async () => {
if(props.item.files) {
availableFiles.value = (await files.selectSomeDocuments(props.item.files.map(i => i.id))) || []
if (props.item.files?.length > 0) {
availableFiles.value =
(await files.selectSomeDocuments(props.item.files.map((f) => f.id))) || []
}
}
setup()
// Datei öffnen (Mobile/Tablet)
function openFile(file) {
activeFile.value = file
showViewer.value = true
}
function closeViewer() {
showViewer.value = false
activeFile.value = null
}
// PDF oder Bild?
function isPdf(file) {
return file.path.includes("pdf")
}
function isImage(file) {
return file.mimetype?.startsWith("image/")
}
</script>
<template>
<UCard class="mt-5" :style="props.platform !== 'mobile' ? 'height: 80vh' : ''">
<template #header v-if="props.platform === 'mobile'">
<template #header>
<span>Dateien</span>
</template>
<!-- Upload -->
<Toolbar>
<DocumentUpload
:type="props.topLevelType.substring(0,props.topLevelType.length-1)"
:type="props.topLevelType.substring(0, props.topLevelType.length - 1)"
:element-id="props.item.id"
@uploadFinished="emit('updateNeeded')"
/>
</Toolbar>
<DocumentList
:key="props.item.files.length"
:documents="availableFiles"
v-if="availableFiles.length > 0"
/>
<UAlert
v-else
icon="i-heroicons-x-mark"
title="Keine Dateien verfügbar"
/>
<!-- 📱 MOBILE: File Cards -->
<div v-if="props.platform === 'mobile'" class="space-y-3 mt-3">
<div
v-for="file in availableFiles"
:key="file.id"
class="p-4 border rounded-xl bg-gray-50 dark:bg-gray-900 flex items-center justify-between active:scale-95 transition cursor-pointer"
@click="openFile(file)"
>
<div>
<p class="font-semibold truncate max-w-[200px]">{{ file?.path?.split("/").pop() }}</p>
</div>
<UIcon
name="i-heroicons-chevron-right-20-solid"
class="w-5 h-5 text-gray-400"
/>
</div>
<UAlert
v-if="!availableFiles.length"
icon="i-heroicons-x-mark"
title="Keine Dateien verfügbar"
/>
</div>
<!-- 🖥 DESKTOP: Classic List -->
<template v-else>
<DocumentList
:key="props.item.files.length"
:documents="availableFiles"
v-if="availableFiles.length > 0"
/>
<UAlert v-else icon="i-heroicons-x-mark" title="Keine Dateien verfügbar" />
</template>
</UCard>
<!-- 📱 PDF / IMG Viewer Slideover -->
<UModal v-model="showViewer" side="bottom" class="h-[100dvh]" fullscreen>
<!-- Header -->
<div class="p-4 border-b flex justify-between items-center flex-shrink-0">
<h3 class="font-bold truncate max-w-[70vw]">{{ activeFile?.path?.split("/").pop() }}</h3>
<UButton icon="i-heroicons-x-mark" variant="ghost" @click="closeViewer" />
</div>
<!-- Content -->
<div class="flex-1 overflow-y-auto m-2">
<!-- PDF -->
<div v-if="activeFile && isPdf(activeFile)" class="h-full">
<PDFViewer
:no-controls="true"
:file-id="activeFile.id"
location="fileviewer-mobile"
class="h-full"
/>
</div>
<!-- IMAGE -->
<div
v-else-if="activeFile && isImage(activeFile)"
class="p-4 flex justify-center"
>
<img
:src="activeFile.url"
class="max-w-full max-h-[80vh] rounded-lg shadow"
/>
</div>
<UAlert
v-else
title="Nicht unterstützter Dateityp"
icon="i-heroicons-exclamation-triangle"
/>
</div>
</UModal>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,67 @@
<script setup>
import { Browser } from "@capacitor/browser"
const props = defineProps({
links: {
type: Array,
required: true,
},
})
/**
* Öffnet externen Link in iOS/Android über InApp Browser.
* Öffnet externen Link im Web über window.open.
* Interne Links → navigateTo
*/
async function openLink(link) {
if (link.external) {
if (useCapacitor().getIsNative()) {
await Browser.open({
url: link.to,
presentationStyle: "popover",
})
} else {
window.open(link.to, "_blank")
}
} else {
return navigateTo(link.to)
}
}
</script>
<template>
<UDashboardCard
v-if="links.length > 0"
title="Schnellzugriffe"
>
<div class="space-y-2">
<div
v-for="(link, index) in links"
:key="index"
class="
p-3 bg-gray-50 dark:bg-gray-900
rounded-xl border flex items-center justify-between
active:scale-95 transition cursor-pointer
"
@click="openLink(link)"
>
<div class="flex items-center gap-3">
<UIcon
:name="link.icon || 'i-heroicons-link'"
class="w-6 h-6 text-primary-500"
/>
<span class="font-medium truncate max-w-[60vw]">
{{ link.label }}
</span>
</div>
<UIcon
name="i-heroicons-chevron-right-20-solid"
class="w-5 h-5 text-gray-400"
/>
</div>
</div>
</UDashboardCard>
</template>

View File

@@ -6,9 +6,9 @@ const auth = useAuthStore()
</script>
<template>
<div>
<div v-if="auth.activeTenant">
<h1 class="font-bold text-xl">Willkommen zurück {{auth.profile.full_name}}</h1>
<span v-if="auth.activeTenant">bei {{auth.activeTenantData.name}}</span>
<span>bei {{auth.activeTenantData.name}}</span>
</div>
</template>

View File

@@ -2,46 +2,65 @@
const props = defineProps({
label: {
type: String,
required: true,
required: false,
default: null
},
icon: {
type: String,
required: false
},
variant: {
type: String,
default: 'solid'
default: "solid"
},
color: {
type: String,
default: 'primary'
default: "primary"
},
pos: {
type: Number,
default: 0
default: 6 // Abstand von unten in Rem (6 = 1.5rem * 6 = 9rem)
}
})
const emit = defineEmits(['click'])
const emit = defineEmits(["click"])
</script>
<template>
<UButton
id="fab"
:icon="props.icon"
:label="props.label"
:variant="props.variant"
:color="props.color"
@click="emit('click')"
:style="`bottom: ${15 + props.pos * 5}vh;`"
class="bg-white dark:bg-gray-950"
/>
<!-- Wrapper für Position + Animation -->
<div
class="fixed right-5 z-40 transition-all"
:style="{ bottom: `calc(${props.pos}rem + env(safe-area-inset-bottom))` }"
>
<UButton
id="fab"
:icon="props.icon"
:label="props.label"
:variant="props.variant"
:color="props.color"
@click="emit('click')"
class="
fab-base
shadow-xl
hover:shadow-2xl
active:scale-95
transition
"
/>
</div>
</template>
<style scoped>
#fab {
position: fixed;
right: 15px;
z-index: 5;
/* FAB Basis */
.fab-base {
@apply rounded-full px-5 py-4 text-lg font-semibold;
/* Wenn nur ein Icon vorhanden ist → runder Kreis */
/* Wenn Label + Icon → Extended FAB */
}
</style>
/* Optional: Auto-Kreisen wenn kein Label */
#fab:not([label]) {
@apply w-14 h-14 p-0 flex items-center justify-center text-2xl;
}
</style>