Rebuild General Layout to Nuxt UI PRO Dashboard

This commit is contained in:
2024-02-22 22:23:15 +01:00
parent c6e0854544
commit 96d4ee7356
14 changed files with 897 additions and 259 deletions

View File

@@ -3,9 +3,7 @@ const supabase = useSupabaseClient()
const user = useSupabaseUser()
const route = useRoute()
const tenants = (await supabase.from("tenants").select()).data
const dataStore = useDataStore()
const viewport = useViewport()
/*watch(viewport.breakpoint, (newBreakpoint, oldBreakpoint) => {
@@ -13,6 +11,10 @@ const viewport = useViewport()
})*/
useHead({
meta: [
{ name: 'viewport', content: 'width=device-width, initial-scale=1' }
@@ -45,6 +47,28 @@ useSeoMeta({
</template>
<style>
/* width */
::-webkit-scrollbar {
width: 5px;
height: 5px;
}
/* Track */
::-webkit-scrollbar-track {
background: rgb(2,6,23);
}
/* Handle */
::-webkit-scrollbar-thumb {
background: grey;
border-radius: 5px;
}
/* Handle on hover */
::-webkit-scrollbar-thumb:hover {
background: #69c350;
}
#logo img{
height: 15vh;
@@ -58,24 +82,12 @@ useSeoMeta({
flex-direction: row;
flex-wrap: wrap;
overflow-y: scroll;
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
.documentList::-webkit-scrollbar {
display: none;
}
.scrollList {
overflow-y: scroll;
height: 85vh;
margin-top: 1em;
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
.scrollList::-webkit-scrollbar {
display: none;
}
@@ -101,16 +113,4 @@ useSeoMeta({
}
.table > div {
width: 78vw;
height: 90vh;
overflow: scroll !important;
-ms-overflow-style: none; /*!* IE and Edge *!*/
scrollbar-width: none; /*!* Firefox *!*/
}
.table > div::-webkit-scrollbar {
display: none;
}
</style>

View File

@@ -0,0 +1,107 @@
<script setup lang="ts">
const { isHelpSlideoverOpen } = useDashboard()
const { metaSymbol } = useShortcuts()
const shortcuts = ref(false)
const query = ref('')
const links = [{
label: 'Shortcuts',
icon: 'i-heroicons-key',
trailingIcon: 'i-heroicons-arrow-right-20-solid',
color: 'gray',
onClick: () => {
shortcuts.value = true
}
}, {
label: 'Documentation',
icon: 'i-heroicons-book-open',
to: 'https://ui.nuxt.com/pro/guide',
target: '_blank'
}, {
label: 'GitHub repository',
icon: 'i-simple-icons-github',
to: 'https://github.com/nuxt/ui-pro',
target: '_blank'
}, {
label: 'Buy Nuxt UI Pro',
icon: 'i-heroicons-credit-card',
to: 'https://ui.nuxt.com/pro/purchase',
target: '_blank'
}]
const categories = computed(() => [{
title: 'General',
items: [
{ shortcuts: [metaSymbol.value, 'K'], name: 'Command menu' },
{ shortcuts: ['N'], name: 'Notifications' },
{ shortcuts: ['?'], name: 'Help & Support' },
{ shortcuts: ['/'], name: 'Search' }
]
}, {
title: 'Navigation',
items: [
{ shortcuts: ['G', 'H'], name: 'Go to Home' },
{ shortcuts: ['G', 'I'], name: 'Go to Inbox' },
{ shortcuts: ['G', 'U'], name: 'Go to Users' },
{ shortcuts: ['G', 'S'], name: 'Go to Settings' }
]
}, {
title: 'Inbox',
items: [
{ shortcuts: ['↑'], name: 'Prev notification' },
{ shortcuts: ['↓'], name: 'Next notification' }
]
}])
const filteredCategories = computed(() => {
return categories.value.map(category => ({
title: category.title,
items: category.items.filter(item => {
return item.name.search(new RegExp(query.value, 'i')) !== -1
})
})).filter(category => !!category.items.length)
})
</script>
<template>
<UDashboardSlideover v-model="isHelpSlideoverOpen">
<template #title>
<UButton
v-if="shortcuts"
color="gray"
variant="ghost"
size="sm"
icon="i-heroicons-arrow-left-20-solid"
@click="shortcuts = false"
/>
{{ shortcuts ? 'Shortcuts' : 'Hilfe & Support' }}
</template>
<div v-if="shortcuts" class="space-y-6">
<UInput v-model="query" icon="i-heroicons-magnifying-glass" placeholder="Search..." autofocus color="gray" />
<div v-for="(category, index) in filteredCategories" :key="index">
<p class="mb-3 text-sm text-gray-900 dark:text-white font-semibold">
{{ category.title }}
</p>
<div class="space-y-2">
<div v-for="(item, i) in category.items" :key="i" class="flex items-center justify-between">
<span class="text-sm text-gray-500 dark:text-gray-400">{{ item.name }}</span>
<div class="flex items-center justify-end flex-shrink-0 gap-0.5">
<UKbd v-for="(shortcut, j) in item.shortcuts" :key="j">
{{ shortcut }}
</UKbd>
</div>
</div>
</div>
</div>
</div>
<div v-else class="flex flex-col gap-y-3">
<UButton v-for="(link, index) in links" :key="index" color="white" v-bind="link" />
</div>
</UDashboardSlideover>
</template>

View File

@@ -0,0 +1,29 @@
<script setup lang="ts">
import { formatTimeAgo } from '@vueuse/core'
import type { Notification } from '~/types'
const { isNotificationsSlideoverOpen } = useDashboard()
const { data: notifications } = await useFetch<Notification[]>('/api/notifications')
</script>
<template>
<UDashboardSlideover v-model="isNotificationsSlideoverOpen" title="Notifications">
<NuxtLink v-for="notification in notifications" :key="notification.id" :to="`/inbox?id=${notification.id}`" class="p-3 rounded-md hover:bg-gray-50 dark:hover:bg-gray-800/50 cursor-pointer flex items-center gap-3 relative">
<UChip color="red" :show="!!notification.unread" inset>
<UAvatar v-bind="notification.sender.avatar" :alt="notification.sender.name" size="md" />
</UChip>
<div class="text-sm flex-1">
<p class="flex items-center justify-between">
<span class="text-gray-900 dark:text-white font-medium">{{ notification.sender.name }}</span>
<time :datetime="notification.date" class="text-gray-500 dark:text-gray-400 text-xs" v-text="formatTimeAgo(new Date(notification.date))" />
</p>
<p class="text-gray-500 dark:text-gray-400">
{{ notification.body }}
</p>
</div>
</NuxtLink>
</UDashboardSlideover>
</template>

View File

@@ -0,0 +1,46 @@
<script setup lang="ts">
const teams = [{
label: 'Nuxt',
/*avatar: {
src: 'https://avatars.githubusercontent.com/u/23360933?s=200&v=4'
},*/
click: () => {
team.value = teams[0]
}
}, {
label: 'NuxtLabs',
/*avatar: {
src: 'https://avatars.githubusercontent.com/u/62017400?s=200&v=4'
},*/
click: () => {
team.value = teams[1]
}
}]
const actions = [{
label: 'Create team',
icon: 'i-heroicons-plus-circle'
}, {
label: 'Manage teams',
icon: 'i-heroicons-cog-8-tooth'
}]
const team = ref(teams[0])
</script>
<template>
<UDropdown
v-slot="{ open }"
mode="hover"
:items="[teams]"
class="w-full"
:ui="{ width: 'w-full' }"
:popper="{ strategy: 'absolute' }"
>
<UButton color="gray" variant="ghost" :class="[open && 'bg-gray-50 dark:bg-gray-800']" class="w-full">
<UAvatar v-if="team.avatar" :src="team.avatar.src" size="2xs" />
<span class="truncate text-gray-900 dark:text-white font-semibold">{{ team.label }}</span>
</UButton>
</UDropdown>
</template>

View File

@@ -0,0 +1,84 @@
<script setup lang="ts">
const { isHelpSlideoverOpen } = useDashboard()
const { isDashboardSearchModalOpen } = useUIState()
const { metaSymbol } = useShortcuts()
const user = useSupabaseUser()
const dataStore = useDataStore()
const supabase = useSupabaseClient()
const router = useRouter()
const items = computed(() => [
[{
slot: 'account',
label: '',
disabled: true
}], [{
label: 'Settings',
icon: 'i-heroicons-cog-8-tooth',
to: '/settings'
}, {
label: 'Command menu',
icon: 'i-heroicons-command-line',
shortcuts: [metaSymbol.value, 'K'],
click: () => {
isDashboardSearchModalOpen.value = true
}
}, {
label: 'Help & Support',
icon: 'i-heroicons-question-mark-circle',
shortcuts: ['?'],
click: () => isHelpSlideoverOpen.value = true
}], [{
label: 'Documentation',
icon: 'i-heroicons-book-open',
to: 'https://ui.nuxt.com/pro/guide',
target: '_blank'
},/* {
label: 'GitHub repository',
icon: 'i-simple-icons-github',
to: 'https://github.com/nuxt/ui-pro',
target: '_blank'
}, {
label: 'Buy Nuxt UI Pro',
icon: 'i-heroicons-credit-card',
to: 'https://ui.nuxt.com/pro/purchase',
target: '_blank'
}*/], [{
label: 'Abmelden',
icon: 'i-heroicons-arrow-left-on-rectangle',
click: async () => {
await supabase.auth.signOut()
await dataStore.clearStore()
await router.push('/login')
}
}]
])
</script>
<template>
<UDropdown mode="hover" :items="items" :ui="{ width: 'w-full', item: { disabled: 'cursor-text select-text' } }" :popper="{ strategy: 'absolute', placement: 'top' }" class="w-full">
<template #default="{ open }">
<UButton color="gray" variant="ghost" class="w-full" :label="dataStore.getProfileById(user.id).fullName" :class="[open && 'bg-gray-50 dark:bg-gray-800']">
<template #leading>
<UAvatar :alt="dataStore.getProfileById(user.id) ? dataStore.getProfileById(user.id).fullName : ''" size="xs" />
</template>
<template #trailing>
<UIcon name="i-heroicons-ellipsis-vertical" class="w-5 h-5 ml-auto" />
</template>
</UButton>
</template>
<template #account>
<div class="text-left">
<p>
Angemeldet als
</p>
<p class="truncate font-medium text-gray-900 dark:text-white">
{{dataStore.getProfileById(user.id).email}}
</p>
</div>
</template>
</UDropdown>
</template>

View File

@@ -0,0 +1,29 @@
import { createSharedComposable } from '@vueuse/core'
const _useDashboard = () => {
const route = useRoute()
const router = useRouter()
const isHelpSlideoverOpen = ref(false)
const isNotificationsSlideoverOpen = ref(false)
defineShortcuts({
'g-h': () => router.push('/'),
'g-i': () => router.push('/inbox'),
'g-u': () => router.push('/users'),
'g-s': () => router.push('/settings'),
'?': () => isHelpSlideoverOpen.value = true,
n: () => isNotificationsSlideoverOpen.value = true
})
watch(() => route.fullPath, () => {
isHelpSlideoverOpen.value = false
isNotificationsSlideoverOpen.value = false
})
return {
isHelpSlideoverOpen,
isNotificationsSlideoverOpen
}
}
export const useDashboard = createSharedComposable(_useDashboard)

View File

@@ -2,7 +2,7 @@
const dataStore = useDataStore()
const colorMode = useColorMode()
const { isHelpSlideoverOpen } = useDashboard()
const userProfile = dataStore.getOwnProfile
const supabase = useSupabaseClient()
const router = useRouter()
@@ -10,6 +10,7 @@ const route = useRoute()
dataStore.initializeData((await supabase.auth.getUser()).data.user.id)
const isLight = computed({
get() {
return colorMode.value !== 'dark'
@@ -196,21 +197,16 @@ const userMenuItems = ref([
}
])
const links = [[{
label: 'Profil',
avatar: {
alt: userProfile ? userProfile.fullName : "XY"
}
},{
const linksold = [[{
label: "Dashboard",
to: "/",
icon: "i-heroicons-home"
}], [{
}],
[{
label: "Aufgaben",
to: "/tasks",
icon: "i-heroicons-rectangle-stack"
},
{
}, {
label: "Plantafel",
to: "/calendar/timeline",
icon: "i-heroicons-calendar-days"
@@ -314,147 +310,227 @@ const links = [[{
}]
]
let links = [
{
id: 'dashboard',
label: "Dashboard",
to: "/",
icon: "i-heroicons-home"
},
{
label: "Organisation",
icon: "i-heroicons-rectangle-stack",
defaultOpen: false,
children: [
{
label: "Aufgaben",
to: "/tasks",
icon: "i-heroicons-rectangle-stack"
},
{
label: "Plantafel",
to: "/calendar/timeline",
icon: "i-heroicons-calendar-days"
},
{
label: "Kalender",
to: "/calendar/grid",
icon: "i-heroicons-calendar-days"
},
{
label: "Dokumente",
to: "/documents",
icon: "i-heroicons-document"
},
]
},
{
label: "E-Mail",
to: "/email",
icon: "i-heroicons-envelope"
},
{
label: "Kontakte",
defaultOpen: false,
icon: "i-heroicons-user-group",
children: [
{
label: "Kunden",
to: "/customers",
icon: "i-heroicons-user-group"
},
{
label: "Lieferanten",
to: "/vendors",
icon: "i-heroicons-truck"
},
{
label: "Ansprechpartner",
to: "/contacts",
icon: "i-heroicons-user-group"
},
]
},
{
label: "Mitarbeiter",
defaultOpen:false,
icon: "i-heroicons-user-group",
children: [
{
label: "Zeiterfassung",
to: "/employees/timetracking",
icon: "i-heroicons-clock"
},
{
label: "Anwesenheiten",
to: "/workingtimes",
icon: "i-heroicons-clock"
},
{
label: "Abwesenheiten",
to: "/absenceRequests",
icon: "i-heroicons-document-text"
},
]
},
{
label: "Lager",
icon: "i-heroicons-puzzle-piece",
defaultOpen: false,
children: [
{
label: "Steuerung",
to: "/inventory",
icon: "i-heroicons-square-3-stack-3d"
},
{
label: "Lagerplätze",
to: "/spaces",
icon: "i-heroicons-square-3-stack-3d"
},
{
label: "Inventar",
to: "/inventoryitems",
icon: "i-heroicons-puzzle-piece"
},
]
},
{
label: "Stammdaten",
defaultOpen: false,
icon: "i-heroicons-clipboard-document",
children: [
{
label: "Artikelstamm",
to: "/products",
icon: "i-heroicons-puzzle-piece"
},{
label: "Leistungsstamm",
to: "/services",
icon: "i-heroicons-puzzle-piece"
},{
label: "Fahrzeuge",
to: "/vehicles",
icon: "i-heroicons-truck"
},
]
},
{
label: "Belege",
to: "/receipts",
icon: "i-heroicons-document-text"
},{
label: "Projekte",
to: "/projects",
icon: "i-heroicons-clipboard-document-check"
},
{
label: "Verträge",
to: "/contracts",
icon: "i-heroicons-clipboard-document"
},
{
label: "Objekte",
to: "/plants",
icon: "i-heroicons-clipboard-document"
},
]
const groups = [{
key: 'links',
label: 'Go to',
commands: links.map(link => ({ ...link, shortcuts: link.tooltip?.shortcuts }))
}, {
key: 'code',
label: 'Code',
commands: [{
id: 'source',
label: 'View page source',
icon: 'i-simple-icons-github',
click: () => {
window.open(`https://github.com/nuxt-ui-pro/dashboard/blob/main/pages${route.path === '/' ? '/index' : route.path}.vue`, '_blank')
}
}]
}]
const footerLinks = [/*{
label: 'Invite people',
icon: 'i-heroicons-plus',
to: '/settings/members'
}, */{
label: 'Hilfe & Support',
icon: 'i-heroicons-question-mark-circle',
click: () => isHelpSlideoverOpen.value = true
}]
</script>
<template>
<div v-if="dataStore.loaded" class="flex justify-center flex-row">
<!-- <UHeader :links="navLinks" :to="null">
<template #logo>
<div id="logo">
<img
:src="!isLight ? '/spaces.svg' : '/spaces_hell.svg'"
alt="Logo"
/>
</div>
<UDashboardLayout v-if="dataStore.loaded">
<UDashboardPanel :width="250" :resizable="{ min: 200, max: 300 }" collapsible>
<UDashboardNavbar class="!border-transparent" :ui="{ left: 'flex-1' }">
<template #left>
<TeamsDropdown />
</template>
</UDashboardNavbar>
<UDashboardSidebar id="sidebar">
<template #header>
<UDashboardSearchButton />
</template>
<template #panel>
<UNavigationTree :links="navLinks"/>
</template>
<UDashboardSidebarLinks :links="links" />
<template #right>
<!--
<UDivider />
<GlobalSearch/>
<UDashboardSidebarLinks :links="[{ label: 'Colors', draggable: true, children: colors }]" @update:links="colors => defaultColors = colors" />
<UButton
@click="showUserMenu = true"
variant="ghost"
color="gray"
>
<UAvatar
:alt="userProfile ? userProfile.firstName + ' ' + userProfile.lastName : '' "
icon="i-heroicons-user-20-solid"
:chip-color="dataStore.notifications.filter(item => !item.read).length > 0 ? 'primary' : null"
/>
</UButton>
<USlideover
v-model="showUserMenu"
>
<UCard
class="h-full"
>
<div v-if="dataStore.getOwnProfile.tenants.length > 1">
<UDivider
class="my-3"
label="Tenant"
/>
<USelectMenu
:options="dataStore.getOwnProfile.tenants"
option-attribute="name"
value-attribute="id"
v-model="dataStore.currentTenant"
@change="dataStore.changeTenant()"
/>
</div>
<UDivider
class="my-3"
label="Menü"
/>
<UVerticalNavigation
:links="userMenuItems"
/>
&lt;!&ndash; <UDivider
class="my-3"
label="Benachrichtigungen"
/>
<UAlert
class="mb-3"
v-for="(notification) in dataStore.notifications"
:color="!notification.read ? 'primary' : 'white'"
:variant="!notification.read ? 'outline' : 'soft'"
:description="notification.text"
:title="notification.title"
:close-button="!notification.read ? { icon: 'i-heroicons-x-mark-20-solid', color: 'gray', variant: 'link', padded: false } : null"
@close="async () => {
const {error} = await supabase.from('notifications').update({read:true}).eq('id', notification.id)
if(error) console.log(error)
dataStore.fetchNotifications()
}"
/>&ndash;&gt;
-->
<div class="flex-1" />
<UDashboardSidebarLinks :links="footerLinks" />
<UDivider class="sticky bottom-0" />
<template #footer>
<InputGroup>
<UButton
:icon="!isLight ? 'i-heroicons-moon-20-solid' : 'i-heroicons-sun-20-solid'"
color="white"
variant="outline"
aria-label="Theme"
@click="isLight = !isLight"
/>
<UButton
color="rose"
variant="outline"
@click="async () => {
showUserMenu = false
await supabase.auth.signOut()
await dataStore.clearStore()
await router.push('/login')
}"
>
Ausloggen
</UButton>
</InputGroup>
<!-- ~/components/UserDropdown.vue -->
<UserDropdown />
</template>
</UDashboardSidebar>
</UDashboardPanel>
<slot />
</UCard>
</USlideover>
<!-- ~/components/HelpSlideover.vue -->
<HelpSlideover />
<!-- ~/components/NotificationsSlideover.vue -->
<NotificationsSlideover />
</template>
</UHeader>
<UDivider />-->
<div class="ml-2 mt-3" id="menuLeft">
<UVerticalNavigation
:links="links"
>
<template #avatar="{link}">
<UAvatar
v-if="link.avatar"
v-bind="link.avatar"
/>
</template>
</UVerticalNavigation>
</div>
<div class="pl-3 pr-3 mt-3" id="contentContainer">
<slot id="content"/>
</div>
</div>
<ClientOnly>
<LazyUDashboardSearch :groups="groups" />
</ClientOnly>
</UDashboardLayout>
<div
v-else
class="flex-col mx-auto my-auto mt-10 w-3/4"
@@ -466,31 +542,9 @@ const links = [[{
/>
<UProgress animation="carousel"/>
</div>
</template>
<style scoped>
#menuLeft {
height: 95vh;
width: 20vw;
overflow-y: scroll;
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
#menuLeft::-webkit-scrollbar {
display: none;
}
#contentContainer {
width: 77vw;
height: 95vh;
overflow-y: scroll;
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
#contentContainer::-webkit-scrollbar {
display: none;
}
</style>

View File

@@ -287,19 +287,28 @@ const calendarOptionsTimeline = reactive({
</UModal>
<div v-if="mode === 'grid'">
<UDashboardPage>
<UDashboardPanel grow>
<UDashboardNavbar :title="currentItem ? currentItem.name : ''">
<template #right>
</template>
</UDashboardNavbar>
<div v-if="mode === 'grid'" class="p-5">
<FullCalendar
:options="calendarOptionsGrid"
/>
</div>
<div v-else-if="mode === 'timeline'">
<div v-else-if="mode === 'timeline'" class="p-5">
<FullCalendar
:options="calendarOptionsTimeline"
/>
</div>
</UDashboardPanel>
</UDashboardPage>
</template>
<style scoped>

View File

@@ -1,26 +1,45 @@
<template>
<div class="cardHolder">
<div class="card">
<h1 class="text-center text-4xl">Aufgaben</h1>
<p class="text-center text-6xl mt-5">{{openTasks}}</p>
</div><div class="card">
<UDashboardPage>
<UDashboardPanel grow>
<UDashboardNavbar title="Home">
<template #right>
<UTooltip text="Notifications" :shortcuts="['N']">
<UButton color="gray" variant="ghost" square @click="isNotificationsSlideoverOpen = true">
<UChip color="red" inset>
<UIcon name="i-heroicons-bell" class="w-5 h-5" />
</UChip>
</UButton>
</UTooltip>
</div><div class="card">
<UDropdown :items="items">
<UButton icon="i-heroicons-plus" size="md" class="ml-1.5 rounded-full" />
</UDropdown>
</template>
</UDashboardNavbar>
</div><div class="card">
<!-- <UDashboardToolbar>
<template #left>
&lt;!&ndash; ~/components/home/HomeDateRangePicker.vue &ndash;&gt;
&lt;!&ndash; <HomeDateRangePicker v-model="range" class="-ml-2.5" />&ndash;&gt;
</div><div class="card">
&lt;!&ndash; ~/components/home/HomePeriodSelect.vue &ndash;&gt;
&lt;!&ndash; <HomePeriodSelect v-model="period" :range="range" />&ndash;&gt;
</template>
</UDashboardToolbar>-->
</div><div class="card">
<UDashboardPanelContent>
<!-- ~/components/home/HomeChart.vue -->
<!-- <HomeChart :period="period" :range="range" />
</div>
<br>
</div>
<div class="grid lg:grid-cols-2 lg:items-start gap-8 mt-8">
&lt;!&ndash; ~/components/home/HomeSales.vue &ndash;&gt;
<HomeSales />
&lt;!&ndash; ~/components/home/HomeCountries.vue &ndash;&gt;
<HomeCountries />
</div>-->
</UDashboardPanelContent>
</UDashboardPanel>
</UDashboardPage>
</template>
<script setup>
@@ -28,6 +47,18 @@ definePageMeta({
middleware: "auth"
})
const { isNotificationsSlideoverOpen } = useDashboard()
const items = [[{
label: 'Aufgabe',
icon: 'i-heroicons-paper-airplane',
to: '/tasks/create'
}, {
label: 'Kunde',
icon: 'i-heroicons-user-plus',
to: '/customers/create'
}]]
const {getOpenTasksCount} = useDataStore()
const openTasks = getOpenTasksCount
@@ -38,7 +69,7 @@ const user = useSupabaseUser()
</script>
<style scoped>
.cardHolder {
/*.cardHolder {
display: flex;
flex-direction: row;
flex-wrap: wrap;
@@ -49,5 +80,5 @@ const user = useSupabaseUser()
width: 22vw;
height: 40vh;
border: 1px solid white
}
}*/
</style>

View File

@@ -122,6 +122,28 @@ const calendarOptionsTimeline = reactive({
</script>
<template>
<UDashboardPage>
<UDashboardPanel grow>
<UDashboardNavbar :title="currentItem ? currentItem.name : ''">
<template #right>
</template>
</UDashboardNavbar>
<div v-if="viewport.isLessThan('tablet')">
<FullCalendar
:options="calendarOptionsList"
/>
</div>
<div v-else>
<FullCalendar
:options="calendarOptionsTimeline"
/>
</div>
</UDashboardPanel>
</UDashboardPage>
<div>
<UModal
v-model="openNewEventModal"
@@ -229,16 +251,7 @@ const calendarOptionsTimeline = reactive({
</UModal>
<div v-if="viewport.isLessThan('tablet')">
<FullCalendar
:options="calendarOptionsList"
/>
</div>
<div v-else>
<FullCalendar
:options="calendarOptionsTimeline"
/>
</div>
</div>

View File

@@ -1,4 +1,6 @@
<script setup>
import dayjs from "dayjs";
definePageMeta({
middleware: "auth"
})
@@ -75,7 +77,177 @@ setupPage()
</script>
<template>
<h1
<UDashboardPage>
<UDashboardPanel grow>
<UDashboardNavbar :title="currentItem ? currentItem.name : ''">
<template #right>
<UButton
v-if="mode === 'edit'"
@click="dataStore.updateItem('tasks',itemInfo)"
>
Speichern
</UButton>
<UButton
v-else-if="mode === 'create'"
@click="dataStore.createNewItem('tasks',itemInfo)"
>
Erstellen
</UButton>
<UButton
@click="cancelEditorCreate"
color="red"
class="ml-2"
v-if="mode === 'edit' || mode === 'create'"
>
Abbrechen
</UButton>
<UButton
v-if="mode === 'show' && currentItem.id"
@click="editItem"
>
Bearbeiten
</UButton>
</template>
</UDashboardNavbar>
<!-- <UDashboardToolbar>
<template #left>
&lt;!&ndash; <UButton @click="router.push(`/tasks/create`)">+ Aufgabe</UButton>
<UInput
v-model="searchString"
placeholder="Suche..."
/>&ndash;&gt;
<UCheckbox
label="Erledigte Anzeigen"
v-model="showDone"
/>
</template>
<template #right>
<USelectMenu
v-model="selectedColumns"
icon="i-heroicons-adjustments-horizontal-solid"
:options="templateColumns"
multiple
class="hidden lg:block"
>
<template #label>
Spalten
</template>
</USelectMenu>
</template>
</UDashboardToolbar>-->
<UTabs
:items="[{label: 'Informationen'},{label: 'Logbuch'}]"
v-if="currentItem && mode === 'show'"
class="p-5"
>
<template #item="{item}">
<UCard class="mt-5">
<div v-if="item.label === 'Informationen'">
<div class="truncate">
<p>Kategorie: {{currentItem.categorie}}</p>
<p v-if="currentItem.project">Projekt: <nuxt-link :to="`/projects/show/${currentItem.project}`">{{dataStore.getProjectById(currentItem.project).name}}</nuxt-link></p>
<p>Beschreibung: {{currentItem.description}}</p>
</div>
</div>
<!-- TODO: Logbuch Tasks -->
</UCard>
</template>
</UTabs>
<UForm v-else-if="mode === 'edit' || mode === 'create' " class="p-5">
<UFormGroup
label="Name:"
>
<UInput
v-model="itemInfo.name"
/>
</UFormGroup>
<UFormGroup
label="Kategorie:"
>
<USelectMenu
v-model="itemInfo.categorie"
:options="categories"
/>
</UFormGroup>
<UFormGroup
label="Benutzer:"
>
<USelectMenu
v-model="itemInfo.user"
:options="dataStore.profiles"
option-attribute="fullName"
value-attribute="id"
searchable-placeholder="Suche..."
searchable
:search-attributes="['fullName']"
>
<template #label>
{{dataStore.getProfileById(itemInfo.user) ? dataStore.getProfileById(itemInfo.user).fullName : "Kein Benutzer ausgewählt"}}
</template>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Projekt:"
>
<USelectMenu
v-model="itemInfo.project"
:options="dataStore.projects"
option-attribute="name"
value-attribute="id"
searchable-placeholder="Suche..."
searchable
:search-attributes="['name']"
>
<template #label>
{{dataStore.getProjectById(itemInfo.project) ? dataStore.getProjectById(itemInfo.project).name : "Kein Projekt ausgewählt"}}
</template>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Objekt:"
>
<USelectMenu
v-model="itemInfo.plant"
:options="dataStore.plants"
option-attribute="name"
value-attribute="id"
searchable-placeholder="Suche..."
searchable
:search-attributes="['name']"
>
<template #label>
{{dataStore.getPlantById(itemInfo.plant) ? dataStore.getPlantById(itemInfo.plant).name : "Kein Objekt ausgewählt"}}
</template>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Beschreibung:"
>
<UTextarea
v-model="itemInfo.description"
/>
</UFormGroup>
</UForm>
</UDashboardPanel>
</UDashboardPage>
<!-- <h1
class="mb-3 truncate font-bold text-2xl"
v-if="currentItem"
>Aufgabe: {{currentItem.name}}</h1>
@@ -108,7 +280,7 @@ setupPage()
</div>
</div>
<!-- TODO: Logbuch Tasks -->
&lt;!&ndash; TODO: Logbuch Tasks &ndash;&gt;
</UCard>
</template>
</UTabs>
@@ -220,7 +392,7 @@ setupPage()
</UButton>
</template>
</UCard>
</UCard>-->
</template>
<style scoped>

View File

@@ -1,25 +1,64 @@
<template>
<UDashboardPage>
<UDashboardPanel grow>
<UDashboardNavbar title="Aufgaben" :badge="filteredRows.length">
<template #right>
<UInput
ref="input"
v-model="searchString"
icon="i-heroicons-funnel"
autocomplete="off"
placeholder="Suche..."
class="hidden lg:block"
@keydown.esc="$event.target.blur()"
>
<template #trailing>
<UKbd value="/" />
</template>
</UInput>
<div>
<Toolbar>
<UButton @click="router.push(`/tasks/create`)">+ Aufgabe</UButton>
</template>
</UDashboardNavbar>
<UDashboardToolbar>
<template #left>
<!-- <UButton @click="router.push(`/tasks/create`)">+ Aufgabe</UButton>
<UInput
v-model="searchString"
placeholder="Suche..."
/>
/>-->
<UCheckbox
label="Erledigte Anzeigen"
v-model="showDone"
/>
</Toolbar>
<div class="table">
</template>
<template #right>
<USelectMenu
v-model="selectedColumns"
icon="i-heroicons-adjustments-horizontal-solid"
:options="templateColumns"
multiple
class="hidden lg:block"
>
<template #label>
Spalten
</template>
</USelectMenu>
</template>
</UDashboardToolbar>
<UTable
v-model:sort="sort"
:rows="filteredRows"
:columns="columns"
@select="selectItem"
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Noch keine Einträge' }"
sort-mode="manual"
class="w-full"
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"
@select="(i) => router.push(`/tasks/show/${i.id}`) "
>
<template #finish-data="{row}">
<UButton
@@ -44,27 +83,37 @@
{{dataStore.getPlantById(row.plant) ? dataStore.getPlantById(row.plant).name : "" }}
</template>
</UTable>
</div>
</div>
</UDashboardPanel>
</UDashboardPage>
</template>
<script setup>
<script lang="ts" setup>
import dayjs from "dayjs";
definePageMeta({
middleware: "auth"
})
const input = ref<{ input: HTMLInputElement }>()
const dataStore = useDataStore()
const router = useRouter()
const mode = ref("show")
const columns = [
{
defineShortcuts({
'/': () => {
input.value?.input?.focus()
},
'+': () => {
router.push("/tasks/create")
}
})
const templateColumns = [
/*{
key:"finish"
},{
},*/{
key: "created_at",
label: "Erstellt am:",
sortable: true
@@ -95,6 +144,10 @@ const columns = [
}
]
const selectedColumns = ref(templateColumns)
const columns = computed(() => templateColumns.filter((column) => selectedColumns.value.includes(column)))
const searchString = ref('')
const showDone = ref(false)

11
spaces/pages/test.vue Normal file
View File

@@ -0,0 +1,11 @@
<script setup lang="ts">
</script>
<template>
test
</template>
<style scoped>
</style>

View File

@@ -795,7 +795,7 @@ export const useDataStore = defineStore('data', () => {
const getResources = computed(() => {
return [
...profiles.value.map(profile => {
...profiles.value.filter(i => i.tenant === currentTenant.value).map(profile => {
return {
type: 'Mitarbeiter',
title: profile.fullName,