This commit is contained in:
2025-03-27 14:33:34 +01:00
parent 7deffc885e
commit 57a4512a0e
8 changed files with 427 additions and 3 deletions

View File

@@ -1,5 +1,6 @@
<script setup> <script setup>
import {useTempStore} from "~/stores/temp.js"; import {useTempStore} from "~/stores/temp.js";
import FloatingActionButton from "~/components/mobile/FloatingActionButton.vue";
const props = defineProps({ const props = defineProps({
type: { type: {
@@ -9,6 +10,9 @@ const props = defineProps({
items: { items: {
required: true, required: true,
type: Array type: Array
},
platform: {
required: true,
} }
}) })
@@ -104,6 +108,12 @@ const filteredRows = computed(() => {
</script> </script>
<template> <template>
<FloatingActionButton
:label="`+ ${dataType.labelSingle}`"
variant="outline"
v-if="platform === 'mobile'"
@click="router.push(`/mobile/standardEntity/${type}/create`)"
/>
<UDashboardNavbar :title="dataType.label" :badge="filteredRows.length"> <UDashboardNavbar :title="dataType.label" :badge="filteredRows.length">
<template #right> <template #right>
<UInput <UInput
@@ -129,7 +139,7 @@ const filteredRows = computed(() => {
/> />
<UButton <UButton
v-if="useRole().checkRight(`${type}-create`)" v-if="platform !== 'mobile' && useRole().checkRight(`${type}-create`)"
@click="router.push(`/standardEntity/${type}/create`)" @click="router.push(`/standardEntity/${type}/create`)"
class="ml-3" class="ml-3"
>+ {{dataType.labelSingle}}</UButton> >+ {{dataType.labelSingle}}</UButton>

View File

@@ -0,0 +1,26 @@
<script setup>
const supabase = useSupabaseClient()
const router = useRouter()
const profileStore = useProfileStore()
const tenant = ref({})
const setupPage = async () => {
tenant.value = (await supabase.from("tenants").select().eq("id",profileStore.currentTenant).single()).data
}
setupPage()
</script>
<template>
<div>
<h1 class="font-bold text-xl">Willkommen zurück {{profileStore.activeProfile.fullName}}</h1>
<span v-if="tenant.id">bei {{tenant.name}}</span>
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,41 @@
<script setup>
const props = defineProps({
label: {
type: String,
required: true,
},
icon: {
type: String,
},
variant: {
type: String,
default: 'solid'
},
color: {
type: String,
default: 'primary'
}
})
const emit = defineEmits(['click'])
</script>
<template>
<UButton
id="fab"
:icon="props.icon"
:label="props.label"
:variant="props.variant"
:color="props.color"
@click="emit('click')"
/>
</template>
<style scoped>
#fab {
position: fixed;
right: 15px;
bottom: 10vh;
}
</style>

View File

@@ -14,11 +14,13 @@ export const useCapacitor = () => {
const getIsPhone = async () => { const getIsPhone = async () => {
let deviceInfo = await useCapacitor().getDeviceInfo() let deviceInfo = await useCapacitor().getDeviceInfo()
if(deviceInfo.model.toLowerCase().includes('iphone')) { /*if(deviceInfo.model.toLowerCase().includes('iphone')) {
return true return true
} else { } else {
return false return false
} }*/
return true
} }
const getNetworkStatus = async () => { const getNetworkStatus = async () => {

242
layouts/mobile.vue Normal file
View File

@@ -0,0 +1,242 @@
<script setup>
import MainNav from "~/components/MainNav.vue";
import dayjs from "dayjs";
import {useProfileStore} from "~/stores/profile.js";
import {useCapacitor} from "../composables/useCapacitor.js";
const dataStore = useDataStore()
const profileStore = useProfileStore()
const colorMode = useColorMode()
const { isHelpSlideoverOpen } = useDashboard()
const supabase = useSupabaseClient()
const router = useRouter()
const route = useRoute()
profileStore.initializeData((await supabase.auth.getUser()).data.user.id)
const month = dayjs().format("MM")
const actions = [
{
id: 'new-customer',
label: 'Kunde hinzufügen',
icon: 'i-heroicons-user-group',
to: "/customers/create" ,
},
{
id: 'new-vendor',
label: 'Lieferant hinzufügen',
icon: 'i-heroicons-truck',
to: "/vendors/create" ,
},
{
id: 'new-contact',
label: 'Ansprechpartner hinzufügen',
icon: 'i-heroicons-user-group',
to: "/contacts/create" ,
},
{
id: 'new-task',
label: 'Aufgabe hinzufügen',
icon: 'i-heroicons-rectangle-stack',
to: "/tasks/create" ,
},
{
id: 'new-plant',
label: 'Objekt hinzufügen',
icon: 'i-heroicons-clipboard-document',
to: "/plants/create" ,
},
{
id: 'new-product',
label: 'Artikel hinzufügen',
icon: 'i-heroicons-puzzle-piece',
to: "/products/create" ,
},
{
id: 'new-project',
label: 'Projekt hinzufügen',
icon: 'i-heroicons-clipboard-document-check',
to: "/projects/create" ,
}
]
const groups = computed(() =>
[{
key: 'actions',
commands: actions
},{
key: "customers",
label: "Kunden",
commands: dataStore.customers.map(item => { return {id: item.id, label: item.name, to: `/customers/show/${item.id}`}})
},{
key: "vendors",
label: "Lieferanten",
commands: dataStore.vendors.map(item => { return {id: item.id, label: item.name, to: `/vendors/show/${item.id}`}})
},{
key: "contacts",
label: "Ansprechpartner",
commands: dataStore.contacts.map(item => { return {id: item.id, label: item.fullName, to: `/contacts/show/${item.id}`}})
},{
key: "products",
label: "Artikel",
commands: dataStore.products.map(item => { return {id: item.id, label: item.name, to: `/products/show/${item.id}`}})
},{
key: "tasks",
label: "Aufgaben",
commands: dataStore.tasks.map(item => { return {id: item.id, label: item.name, to: `/tasks/show/${item.id}`}})
},{
key: "plants",
label: "Objekte",
commands: dataStore.plants.map(item => { return {id: item.id, label: item.name, to: `/plants/show/${item.id}`}})
},{
key: "projects",
label: "Projekte",
commands: dataStore.projects.map(item => { return {id: item.id, label: item.name, to: `/projects/show/${item.id}`}})
}
].filter(Boolean))
const footerLinks = [/*{
label: 'Invite people',
icon: 'i-heroicons-plus',
to: '/settings/members'
}, */{
label: 'Hilfe & Info',
icon: 'i-heroicons-question-mark-circle',
click: () => isHelpSlideoverOpen.value = true
}]
</script>
<template>
<UDashboardLayout class="safearea" v-if="profileStore.loaded">
<UDashboardPanel :width="250" :resizable="{ min: 200, max: 300 }" collapsible>
<UDashboardNavbar style="margin-top: env(safe-area-inset-top, 10px) !important;" :class="['!border-transparent']" :ui="{ left: 'flex-1' }">
<template #left>
<ProfileDropdown class="w-full" />
</template>
</UDashboardNavbar>
<UDashboardSidebar id="sidebar">
<template #header>
<UDashboardSearchButton v-if="!useCapacitor().getIsPhone()" label="Suche..."/>
</template>
<MainNav/>
<div class="flex-1" />
<template #footer>
<div class="flex flex-col w-full">
<UDashboardSidebarLinks :links="footerLinks" />
<UDivider class="sticky bottom-0" />
<UserDropdown style="margin-bottom: env(safe-area-inset-bottom, 10px) !important;"/>
</div>
</template>
</UDashboardSidebar>
</UDashboardPanel>
<UDashboardPage style="height: 92vh">
<UDashboardPanel grow>
<slot />
</UDashboardPanel>
</UDashboardPage>
<div class="mobileFooter">
<UButton
icon="i-heroicons-home"
to="/mobile/"
variant="ghost"
:color="route.fullPath === '/mobile' ? 'primary' : 'gray'"
/>
<UButton
icon="i-heroicons-rectangle-stack"
to="/standardEntity/tasks"
variant="ghost"
:color="route.fullPath === '/standardEntity/tasks' ? 'primary' : 'gray'"
/>
<UButton
icon="i-heroicons-clipboard-document-check"
to="/standardEntity/projects"
variant="ghost"
:color="route.fullPath === '/standardEntity/projects' ? 'primary' : 'gray'"
/>
<UButton
icon="i-heroicons-clock"
to="/workingtimes"
variant="ghost"
:color="route.fullPath === '/workingtimes' ? 'primary' : 'gray'"
/>
<UButton
icon="i-heroicons-bars-4"
to="/mobile/menu"
variant="ghost"
:color="route.fullPath === '/mobile/menu' ? 'primary' : 'gray'"
/>
</div>
<!-- ~/components/HelpSlideover.vue -->
<HelpSlideover/>
<!-- ~/components/NotificationsSlideover.vue -->
<NotificationsSlideover />
<ClientOnly>
<LazyUDashboardSearch :groups="groups" hide-color-mode/>
</ClientOnly>
</UDashboardLayout>
<div
v-else
class="flex flex-col"
>
<UColorModeImage
light="/Logo_Hell_Weihnachten.png"
dark="/Logo_Dunkel_Weihnachten.png"
class="w-1/3 mx-auto my-10"
v-if="month === '12'"
/>
<UColorModeImage
light="/Logo.png"
dark="/Logo_Dark.png"
class="w-1/3 mx-auto my-10"
v-else
/>
<div v-if="dataStore.showProfileSelection">
<ProfileSelection/>
</div>
<div v-else>
<UProgress animation="carousel" class="w-3/4 mx-auto mt-10" />
</div>
</div>
</template>
<style scoped>
.mobileFooter {
position: absolute;
bottom: 0;
left: 0;
height: 8vh;
width: 100%;
border-top: 1px solid grey;
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 1em;
background-color: white;
}
.mobileFooter > a {
}
</style>

48
pages/mobile/index.vue Normal file
View File

@@ -0,0 +1,48 @@
<script setup>
import DisplayPresentProfiles from "~/components/noAutoLoad/displayPresentProfiles.vue";
definePageMeta({
layout: 'mobile'
})
const profileStore = useProfileStore()
</script>
<template>
<UDashboardPanelContent>
<UPageGrid>
<UDashboardCard>
<display-welcome/>
</UDashboardCard>
<UDashboardCard
title="Aufgaben"
>
<display-open-tasks/>
</UDashboardCard>
<UDashboardCard
title="Anwesenheit"
>
<display-running-time/>
</UDashboardCard>
<UDashboardCard
title="Buchhaltung"
v-if="profileStore.ownTenant.features.accounting"
>
<display-open-balances/>
</UDashboardCard>
<UDashboardCard
title="Projekte"
>
<display-projects-in-phases/>
</UDashboardCard>
</UPageGrid>
</UDashboardPanelContent>
</template>
<style scoped>
</style>

39
pages/mobile/menu.vue Normal file
View File

@@ -0,0 +1,39 @@
<script setup>
definePageMeta({
layout: 'mobile',
})
</script>
<template>
<UDashboardPanelContent>
<UButton
class="w-full my-1">
Abwesenheiten
</UButton>
<UButton
class="w-full my-1">
Kalender
</UButton>
<UButton
class="w-full my-1">
Kunden
</UButton>
<UButton
class="w-full my-1">
Lieferanten
</UButton>
<UButton
class="w-full my-1">
Objekte
</UButton>
</UDashboardPanelContent>
</template>
<style scoped>
</style>

View File

@@ -1,9 +1,19 @@
<script setup> <script setup>
import {setPageLayout} from "#app";
import {useCapacitor} from "~/composables/useCapacitor.js";
definePageMeta({
middleware: "auth",
layout: "default",
})
const route = useRoute() const route = useRoute()
const dataStore = useDataStore() const dataStore = useDataStore()
const supabase = useSupabaseClient() const supabase = useSupabaseClient()
const type = route.params.type const type = route.params.type
const platform = useCapacitor().getIsPhone() ? "mobile" : "default"
const dataType = dataStore.dataTypes[route.params.type] const dataType = dataStore.dataTypes[route.params.type]
@@ -13,6 +23,11 @@ const items = ref([])
const item = ref({}) const item = ref({})
const setupPage = async () => { const setupPage = async () => {
if(platform === "mobile") {
setPageLayout("mobile")
}
if(route.params.mode) mode.value = route.params.mode if(route.params.mode) mode.value = route.params.mode
if(mode.value === "show") { if(mode.value === "show") {
@@ -58,6 +73,7 @@ setupPage()
v-else-if="loaded && mode === 'list'" v-else-if="loaded && mode === 'list'"
:type="type" :type="type"
:items="items" :items="items"
:platform="platform"
/> />
<UProgress <UProgress
v-else v-else