Start UI Change

This commit is contained in:
2026-03-21 21:13:22 +01:00
parent cfd84b773f
commit b009ac845f
65 changed files with 2837 additions and 2114 deletions

View File

@@ -1,7 +1,9 @@
export default defineAppConfig({
ui: {
primary: 'green',
gray: 'slate',
colors: {
primary: 'green',
neutral: 'slate'
},
tooltip: {
background: '!bg-background'
},

View File

@@ -0,0 +1,26 @@
@import "tailwindcss";
@import "@nuxt/ui-pro";
@theme static {
--font-sans: "SF Pro Text", "SF Pro Display", "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
--font-mono: "SF Mono", "Cascadia Code", "JetBrains Mono", Consolas, "Liberation Mono", Menlo, monospace;
--color-green-50: #f4fbf2;
--color-green-100: #e7f7e1;
--color-green-200: #cdeec4;
--color-green-300: #a6e095;
--color-green-400: #69c350;
--color-green-500: #53ad3a;
--color-green-600: #418e2b;
--color-green-700: #357025;
--color-green-800: #2d5922;
--color-green-900: #254a1d;
--color-green-950: #10280b;
}
:root {
--ui-container: 90rem;
}
body {
font-family: var(--font-sans);
}

View File

@@ -38,7 +38,7 @@ const emitConfirm = () => {
>
Archivieren
</UButton>
<UModal v-model="showModal">
<UModal v-model:open="showModal">
<UCard>
<template #header>
<span class="text-md font-bold">Archivieren bestätigen</span>

View File

@@ -140,7 +140,7 @@ loadAccounts()
</InputGroup>
</div>
<UModal v-model="showCreate">
<UModal v-model:open="showCreate">
<UCard>
<template #header>Neue Bankverbindung erstellen</template>
<div class="space-y-3">

View File

@@ -31,33 +31,35 @@ const emitConfirm = () => {
>
<slot name="button"></slot>
</UButton>
<UModal v-model="showModal">
<UCard>
<template #header>
<slot name="header"></slot>
</template>
<slot/>
<template #footer>
<div class="text-right">
<UButtonGroup>
<UButton
variant="outline"
@click="showModal = false"
>
Abbrechen
</UButton>
<UButton
@click="emitConfirm"
class="ml-2"
color="rose"
>
Archivieren
</UButton>
</UButtonGroup>
<UModal v-model:open="showModal">
<template #content>
<UCard>
<template #header>
<slot name="header"></slot>
</template>
<slot/>
<template #footer>
<div class="text-right">
<UButtonGroup>
<UButton
variant="outline"
@click="showModal = false"
>
Abbrechen
</UButton>
<UButton
@click="emitConfirm"
class="ml-2"
color="rose"
>
Archivieren
</UButton>
</UButtonGroup>
</div>
</template>
</UCard>
</div>
</template>
</UCard>
</template>
</UModal>
</template>

View File

@@ -227,9 +227,14 @@ defineShortcuts({
width: 4px;
}
.custom-scrollbar::-webkit-scrollbar-track {
@apply bg-transparent;
background: transparent;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
@apply bg-gray-200 dark:bg-gray-700 rounded-full;
background: #e5e7eb;
border-radius: 9999px;
}
:global(.dark) .custom-scrollbar::-webkit-scrollbar-thumb {
background: #374151;
}
</style>

View File

@@ -156,7 +156,8 @@ const moveFile = async () => {
<template>
<UModal fullscreen >
<UCard :ui="{ body: { base: 'flex-1' }, ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }" class="h-full">
<template #content>
<UCard :ui="{ body: { base: 'flex-1' }, ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }" class="h-full">
<template #header>
<div class="flex flex-row justify-between">
<div class="flex items-center gap-2">
@@ -351,7 +352,8 @@ const moveFile = async () => {
</div>
</div>
</UCard>
</UCard>
</template>
</UModal>
</template>

View File

@@ -78,81 +78,83 @@ const fileNames = computed(() => {
<template>
<UModal>
<div ref="dropZoneRef" class="relative h-full flex flex-col">
<template #content>
<div ref="dropZoneRef" class="relative h-full flex flex-col">
<div
v-if="isOverDropZone"
class="absolute inset-0 z-50 flex items-center justify-center bg-primary-500/10 border-2 border-primary-500 border-dashed rounded-lg backdrop-blur-sm transition-all"
>
<span class="text-xl font-bold text-primary-600 bg-white/80 px-4 py-2 rounded shadow-sm">
Dateien hier ablegen
</span>
</div>
<UCard :ui="{ body: { base: 'flex-1' }, ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
<template #header>
<div class="flex items-center justify-between">
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">
Datei hochladen
</h3>
<UButton
color="gray"
variant="ghost"
icon="i-heroicons-x-mark-20-solid"
class="-my-1"
@click="modal.close()"
:disabled="uploadInProgress"
/>
</div>
</template>
<UFormGroup
label="Datei:"
:help="selectedFiles.length > 0 ? `${selectedFiles.length} Datei(en) ausgewählt` : 'Ziehen Sie Dateien hierher oder klicken Sie'"
<div
v-if="isOverDropZone"
class="absolute inset-0 z-50 flex items-center justify-center bg-primary-500/10 border-2 border-primary-500 border-dashed rounded-lg backdrop-blur-sm transition-all"
>
<UInput
v-if="selectedFiles.length === 0"
type="file"
id="fileUploadInput"
multiple
accept="image/jpeg, image/png, image/gif, application/pdf"
@change="onFileInputChange"
/>
<span class="text-xl font-bold text-primary-600 bg-white/80 px-4 py-2 rounded shadow-sm">
Dateien hier ablegen
</span>
</div>
<div v-if="selectedFiles.length > 0" class="mt-2 text-sm text-gray-500">
Ausgewählt: <span class="font-medium text-gray-700 dark:text-gray-300">{{ fileNames }}</span>
</div>
</UFormGroup>
<UCard :ui="{ body: { base: 'flex-1' }, ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
<template #header>
<div class="flex items-center justify-between">
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">
Datei hochladen
</h3>
<UButton
color="gray"
variant="ghost"
icon="i-heroicons-x-mark-20-solid"
class="-my-1"
@click="modal.close()"
:disabled="uploadInProgress"
/>
</div>
</template>
<UFormGroup
label="Typ:"
class="mt-3"
>
<USelectMenu
option-attribute="name"
value-attribute="id"
searchable
searchable-placeholder="Suchen..."
:options="availableFiletypes"
v-model="props.fileData.type"
:disabled="!props.fileData.typeEnabled"
<UFormGroup
label="Datei:"
:help="selectedFiles.length > 0 ? `${selectedFiles.length} Datei(en) ausgewählt` : 'Ziehen Sie Dateien hierher oder klicken Sie'"
>
<template #label>
<span v-if="availableFiletypes.find(x => x.id === props.fileData.type)">{{availableFiletypes.find(x => x.id === props.fileData.type).name}}</span>
<span v-else>Kein Typ ausgewählt</span>
</template>
</USelectMenu>
</UFormGroup>
<UInput
v-if="selectedFiles.length === 0"
type="file"
id="fileUploadInput"
multiple
accept="image/jpeg, image/png, image/gif, application/pdf"
@change="onFileInputChange"
/>
<template #footer>
<UButton
@click="uploadFiles"
:loading="uploadInProgress"
:disabled="uploadInProgress || selectedFiles.length === 0"
>Hochladen</UButton>
</template>
</UCard>
</div>
<div v-if="selectedFiles.length > 0" class="mt-2 text-sm text-gray-500">
Ausgewählt: <span class="font-medium text-gray-700 dark:text-gray-300">{{ fileNames }}</span>
</div>
</UFormGroup>
<UFormGroup
label="Typ:"
class="mt-3"
>
<USelectMenu
option-attribute="name"
value-attribute="id"
searchable
searchable-placeholder="Suchen..."
:options="availableFiletypes"
v-model="props.fileData.type"
:disabled="!props.fileData.typeEnabled"
>
<template #label>
<span v-if="availableFiletypes.find(x => x.id === props.fileData.type)">{{availableFiletypes.find(x => x.id === props.fileData.type).name}}</span>
<span v-else>Kein Typ ausgewählt</span>
</template>
</USelectMenu>
</UFormGroup>
<template #footer>
<UButton
@click="uploadFiles"
:loading="uploadInProgress"
:disabled="uploadInProgress || selectedFiles.length === 0"
>Hochladen</UButton>
</template>
</UCard>
</div>
</template>
</UModal>
</template>

View File

@@ -191,14 +191,14 @@ const filteredRows = computed(() => {
<EntityTableMobile
v-if="platform === 'mobile'"
:type="props.type"
:columns="columns"
:columns="normalizeTableColumns(columns)"
:rows="filteredRows"
/>
<EntityTable
v-else
@sort="(i) => emit('sort',i)"
:type="props.type"
:columns="columns"
:columns="normalizeTableColumns(columns)"
:rows="filteredRows"
:loading="props.loading"
/>

View File

@@ -114,7 +114,7 @@ setup()
<div class="scroll" style="height: 70vh">
<EntityTable
:type="type"
:columns="columns"
:columns="normalizeTableColumns(columns)"
:rows="props.item[type]"
style
/>

View File

@@ -249,7 +249,7 @@ const selectItem = (item) => {
</Toolbar>
<UTable
:rows="props.item.createddocuments.filter(i => !i.archived)"
:columns="columns"
:columns="normalizeTableColumns(columns)"
class="w-full"
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"
@select="selectItem"

View File

@@ -94,7 +94,7 @@ function isImage(file) {
</UCard>
<!-- 📱 PDF / IMG Viewer Slideover -->
<UModal v-model="showViewer" side="bottom" class="h-[100dvh]" fullscreen>
<UModal v-model:open="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>

View File

@@ -78,7 +78,7 @@ const renderedAllocations = computed(() => {
<UTable
v-if="props.item.statementallocations"
:rows="renderedAllocations"
:columns="[{key:'amount', label:'Betrag'},{key:'date', label:'Datum'},{key:'partner', label:'Partner'},{key:'description', label:'Beschreibung'}]"
:columns="normalizeTableColumns([{key:'amount', label:'Betrag'},{key:'date', label:'Datum'},{key:'partner', label:'Partner'},{key:'description', label:'Beschreibung'}])"
@select="(i) => selectAllocation(i)"
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Buchungen anzuzeigen' }"
>

View File

@@ -95,26 +95,26 @@ const changeActivePhase = async (key) => {
<UAccordion
:items="renderedPhases"
>
<template #default="{item,index,open}">
<template #default="slotProps">
<UButton
variant="ghost"
:color="item.active ? 'primary' : 'white'"
:color="slotProps.item.active ? 'primary' : 'white'"
class="mb-1"
:disabled="true"
>
<template #leading>
<div class="w-6 h-6 flex items-center justify-center -my-1">
<UIcon :name="item.icon" class="w-4 h-4 " />
<UIcon :name="slotProps.item.icon" class="w-4 h-4 " />
</div>
</template>
<span class="truncate"> {{item.label}}</span>
<span class="truncate"> {{ slotProps.item.label }}</span>
<template #trailing>
<UIcon
name="i-heroicons-chevron-right-20-solid"
class="w-5 h-5 ms-auto transform transition-transform duration-200"
:class="[open && 'rotate-90']"
:class="[slotProps?.open && 'rotate-90']"
/>
</template>

View File

@@ -67,7 +67,7 @@ const columns = [
<UCard class="mt-5">
<UTable
class="mt-3"
:columns="columns"
:columns="normalizeTableColumns(columns)"
:rows="props.item.times"
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Noch keine Einträge' }"
>

View File

@@ -62,6 +62,7 @@
column: dataType.sortColumn || "date",
direction: 'desc'
})
const normalizedColumns = computed(() => normalizeTableColumns(props.columns))
</script>
@@ -74,7 +75,7 @@
@update:sort="emit('sort',{sort_column: sort.column, sort_direction: sort.direction})"
v-if="dataType && columns"
:rows="props.rows"
:columns="props.columns"
:columns="normalizedColumns"
class="w-full"
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"
@select="(i) => router.push(getShowRoute(type, i.id))"

View File

@@ -55,17 +55,19 @@ setup()
</script>
<template>
<UModal v-model="showMessageModal" prevent-close>
<UCard>
<template #header>
<span class="font-bold">{{messageToShow.title}}</span>
</template>
<p class=" my-2" v-html="messageToShow.description"></p>
<UButton
variant="outline"
@click="markMessageAsRead"
>Gelesen</UButton>
</UCard>
<UModal v-model:open="showMessageModal" prevent-close>
<template #content>
<UCard>
<template #header>
<span class="font-bold">{{messageToShow.title}}</span>
</template>
<p class=" my-2" v-html="messageToShow.description"></p>
<UButton
variant="outline"
@click="markMessageAsRead"
>Gelesen</UButton>
</UCard>
</template>
</UModal>
<!-- <UCard
@@ -79,7 +81,7 @@ setup()
variant="ghost"
@click="showMessage(globalMessages[0])"
/>
<UModal v-model="showMessageModal">
<UModal v-model:open="showMessageModal">
<UCard>
<template #header>
<span class="font-bold">{{messageToShow.title}}</span>

View File

@@ -125,13 +125,15 @@ function onSelect (option) {
<UModal
v-model="showCommandPalette"
>
<UCommandPalette
v-model="selectedCommand"
:groups="groups"
:autoselect="false"
@update:model-value="onSelect"
ref="commandPaletteRef"
/>
<template #content>
<UCommandPalette
v-model="selectedCommand"
:groups="groups"
:autoselect="false"
@update:model-value="onSelect"
ref="commandPaletteRef"
/>
</template>
</UModal>
</template>

View File

@@ -2,9 +2,16 @@
import dayjs from 'dayjs'
const { isHelpSlideoverOpen } = useDashboard()
const { metaSymbol } = useShortcuts()
const { entries, pending, error, seenState, refresh, markAsSeen } = useChangelog()
const metaSymbol = computed(() => {
if (import.meta.server) {
return 'Ctrl'
}
return /Mac|iPhone|iPad|iPod/i.test(navigator.platform) ? '⌘' : 'Ctrl'
})
const shortcuts = ref(false)
const query = ref('')
const toast = useToast()
@@ -154,7 +161,7 @@ watch(isHelpSlideoverOpen, async (isOpen) => {
</script>
<template>
<UDashboardSlideover v-model="isHelpSlideoverOpen">
<USlideover v-model:open="isHelpSlideoverOpen" side="right">
<template #title>
<UButton
v-if="shortcuts"
@@ -168,93 +175,94 @@ watch(isHelpSlideoverOpen, async (isOpen) => {
{{ shortcuts ? 'Shortcuts' : 'Hilfe & Information' }}
</template>
<div v-if="shortcuts" class="space-y-6">
<UInput v-model="query" icon="i-heroicons-magnifying-glass" placeholder="Search..." autofocus color="gray" />
<template #body>
<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 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="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-6">
<div class="flex flex-col gap-y-3">
<UButton v-for="(link, index) in links" :key="index" color="white" v-bind="link" />
</div>
<UCard>
<div class="flex items-center justify-between gap-3">
<div>
<p class="text-base font-semibold text-gray-900 dark:text-white">
Changelog
</p>
<p class="text-sm text-gray-500 dark:text-gray-400">
Zuletzt geöffnet: {{ lastOpenedLabel }}
</p>
</div>
<UButton
icon="i-heroicons-arrow-path"
color="gray"
variant="ghost"
:loading="pending"
@click="refresh(true)"
/>
</div>
<UAlert
v-if="error"
class="mt-4"
color="red"
variant="soft"
title="Changelog konnte nicht geladen werden"
:description="error"
/>
<div v-else-if="pending && !changelogEntries.length" class="mt-4">
<UProgress animation="carousel"/>
</div>
<div v-else-if="changelogEntries.length" class="mt-4 flex flex-col gap-3">
<div
v-for="entry in changelogEntries"
:key="entry.hash"
class="rounded-lg border border-gray-200 dark:border-gray-800 p-3"
>
<div class="flex items-start justify-between gap-3">
<div class="min-w-0">
<p class="font-medium text-gray-900 dark:text-white break-words">
{{ entry.subject }}
</p>
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">
{{ entry.authorName }} · {{ dayjs(entry.committedAt).format('DD.MM.YYYY HH:mm') }}
</p>
<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>
<UBadge color="gray" variant="subtle">
{{ entry.shortHash }}
</UBadge>
</div>
</div>
</div>
</div>
<div v-else class="flex flex-col gap-y-6">
<div class="flex flex-col gap-y-3">
<UButton v-for="(link, index) in links" :key="index" color="white" v-bind="link" />
</div>
<p v-else class="mt-4 text-sm text-gray-500 dark:text-gray-400">
Es sind noch keine Changelog-Einträge verfügbar.
</p>
</UCard>
</div>
<UCard>
<div class="flex items-center justify-between gap-3">
<div>
<p class="text-base font-semibold text-gray-900 dark:text-white">
Changelog
</p>
<p class="text-sm text-gray-500 dark:text-gray-400">
Zuletzt geöffnet: {{ lastOpenedLabel }}
</p>
</div>
<UButton
icon="i-heroicons-arrow-path"
color="gray"
variant="ghost"
:loading="pending"
@click="refresh(true)"
/>
</div>
<UAlert
v-if="error"
class="mt-4"
color="red"
variant="soft"
title="Changelog konnte nicht geladen werden"
:description="error"
/>
<div v-else-if="pending && !changelogEntries.length" class="mt-4">
<UProgress animation="carousel"/>
</div>
<div v-else-if="changelogEntries.length" class="mt-4 flex flex-col gap-3">
<div
v-for="entry in changelogEntries"
:key="entry.hash"
class="rounded-lg border border-gray-200 dark:border-gray-800 p-3"
>
<div class="flex items-start justify-between gap-3">
<div class="min-w-0">
<p class="font-medium text-gray-900 dark:text-white break-words">
{{ entry.subject }}
</p>
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">
{{ entry.authorName }} · {{ dayjs(entry.committedAt).format('DD.MM.YYYY HH:mm') }}
</p>
</div>
<UBadge color="gray" variant="subtle">
{{ entry.shortHash }}
</UBadge>
</div>
</div>
</div>
<p v-else class="mt-4 text-sm text-gray-500 dark:text-gray-400">
Es sind noch keine Changelog-Einträge verfügbar.
</p>
</UCard>
</div>
<!-- <div class="mt-5" v-if="!loadingContactRequest">
<h1 class="font-semibold">Kontaktanfrage:</h1>
<UForm
@@ -305,5 +313,6 @@ watch(isHelpSlideoverOpen, async (isOpen) => {
</UForm>
</div>
<UProgress class="mt-5" animation="carousel" v-else/>-->
</UDashboardSlideover>
</template>
</USlideover>
</template>

View File

@@ -79,35 +79,37 @@ const renderText = (text) => {
v-model="showAddHistoryItemModal"
>
<UCard class="h-full">
<template #header>
<div class="flex items-center justify-between">
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">
Eintrag hinzufügen
</h3>
<UButton color="gray" variant="ghost" icon="i-heroicons-x-mark-20-solid" class="-my-1" @click="showAddHistoryItemModal = false" />
</div>
</template>
<template #content>
<UCard class="h-full">
<template #header>
<div class="flex items-center justify-between">
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">
Eintrag hinzufügen
</h3>
<UButton color="gray" variant="ghost" icon="i-heroicons-x-mark-20-solid" class="-my-1" @click="showAddHistoryItemModal = false" />
</div>
</template>
<UFormGroup
label="Text:"
>
<UTextarea
v-model="addHistoryItemData.text"
@keyup.meta.enter="addHistoryItem"
/>
<!-- TODO: Add Dropdown and Checking for Usernames -->
<!-- <template #help>
<UKbd>{{metaSymbol}}</UKbd> <UKbd>Enter</UKbd> Speichern
</template>-->
<UFormGroup
label="Text:"
>
<UTextarea
v-model="addHistoryItemData.text"
@keyup.meta.enter="addHistoryItem"
/>
<!-- TODO: Add Dropdown and Checking for Usernames -->
<!-- <template #help>
<UKbd>{{metaSymbol}}</UKbd> <UKbd>Enter</UKbd> Speichern
</template>-->
</UFormGroup>
</UFormGroup>
<template #footer>
<UButton @click="addHistoryItem">Speichern</UButton>
</template>
</UCard>
<template #footer>
<UButton @click="addHistoryItem">Speichern</UButton>
</template>
</UCard>
</template>
</UModal>
<Toolbar
v-if="!props.renderHeadline && props.elementId && props.type"

View File

@@ -90,7 +90,8 @@ watch(() => labelPrinter.connected, (connected) => {
<template>
<UModal :ui="{ width: 'sm:max-w-5xl' }">
<UCard class="w-[92vw] max-w-5xl">
<template #content>
<UCard class="w-[92vw] max-w-5xl">
<template #header>
<div class="flex items-center justify-between">
@@ -133,6 +134,7 @@ watch(() => labelPrinter.connected, (connected) => {
</div>
</template>
</UCard>
</UCard>
</template>
</UModal>
</template>

View File

@@ -18,21 +18,23 @@ const handleClick = async () => {
<template>
<!-- Printer Button -->
<UModal v-model="showPrinterInfo">
<UCard>
<template #header>
<div class="flex items-center justify-between">
<h3 class="text-lg font-semibold">Drucker Informationen</h3>
<UButton icon="i-heroicons-x-mark" variant="ghost" @click="showPrinterInfo = false" />
</div>
</template>
<p>Seriennummer: {{labelPrinter.info.serial}}</p>
<p>MAC: {{labelPrinter.info.mac}}</p>
<p>Modell: {{labelPrinter.info.modelId}}</p>
<p>Charge: {{labelPrinter.info.charge}}</p>
<p>Hardware Version: {{labelPrinter.info.hardwareVersion}}</p>
<p>Software Version: {{labelPrinter.info.softwareVersion}}</p>
</UCard>
<UModal v-model:open="showPrinterInfo">
<template #content>
<UCard>
<template #header>
<div class="flex items-center justify-between">
<h3 class="text-lg font-semibold">Drucker Informationen</h3>
<UButton icon="i-heroicons-x-mark" variant="ghost" @click="showPrinterInfo = false" />
</div>
</template>
<p>Seriennummer: {{labelPrinter.info.serial}}</p>
<p>MAC: {{labelPrinter.info.mac}}</p>
<p>Modell: {{labelPrinter.info.modelId}}</p>
<p>Charge: {{labelPrinter.info.charge}}</p>
<p>Hardware Version: {{labelPrinter.info.hardwareVersion}}</p>
<p>Software Version: {{labelPrinter.info.softwareVersion}}</p>
</UCard>
</template>
</UModal>
<UButton

View File

@@ -1,10 +1,14 @@
<script setup>
const props = defineProps({
collapsed: {
type: Boolean,
default: false
}
})
const route = useRoute()
const auth = useAuthStore()
const { has } = usePermission()
// Lokaler State für den Taschenrechner
const showCalculator = ref(false)
const tenantExtraModules = computed(() => {
const modules = auth.activeTenantData?.extraModules
return Array.isArray(modules) ? modules : []
@@ -19,6 +23,17 @@ const isAdmin = computed(() => Boolean(auth.user?.is_admin))
const tenantFeatures = computed(() => auth.activeTenantData?.features || {})
const featureEnabled = (key) => tenantFeatures.value?.[key] !== false
const visibleItems = (items) => items.filter(item => item && !item.disabled)
const isRouteActive = (to) => {
if (!to) {
return false
}
if (to === '/') {
return route.path === '/'
}
return route.path === to || route.path.startsWith(`${to}/`)
}
const links = computed(() => {
const organisationChildren = [
@@ -284,15 +299,13 @@ const links = computed(() => {
label: pin.label,
to: pin.link,
icon: pin.icon,
target: "_blank",
pinned: true
target: "_blank"
}
} else if (pin.type === "standardEntity") {
return {
label: pin.label,
to: pin.datatype === "tasks" ? `/tasks/show/${pin.id}` : `/standardEntity/${pin.datatype}/show/${pin.id}`,
icon: pin.icon,
pinned: true
icon: pin.icon
}
}
}),
@@ -382,81 +395,80 @@ const links = computed(() => {
])
})
const accordionItems = computed(() =>
links.value.filter(item => Array.isArray(item.children) && item.children.length > 0)
)
const navItems = computed(() =>
links.value
.filter(Boolean)
.map((item, index) => {
const children = Array.isArray(item.children)
? item.children.map((child, childIndex) => ({
...child,
value: child.id || child.label || `${index}-${childIndex}`,
active: isRouteActive(child.to)
}))
: undefined
const buttonItems = computed(() =>
links.value.filter(item => !item.children || item.children.length === 0)
const active = item.active || isRouteActive(item.to) || Boolean(children?.some(child => child.active))
return {
...item,
children,
value: item.id || item.label || String(index),
defaultOpen: item.defaultOpen || active,
active,
tooltip: true,
popover: true,
trailingIcon: children?.length ? undefined : ''
}
})
)
</script>
<template>
<div class="flex flex-col gap-1">
<UButton
v-for="item in buttonItems"
:key="item.label"
variant="ghost"
:color="(item.to && route.path === item.to) ? 'primary' : (item.pinned ? 'amber' : 'gray')"
:icon="item.pinned ? 'i-heroicons-star' : item.icon"
class="w-full"
:to="item.to"
:target="item.target"
@click="item.click ? item.click() : null"
>
<UIcon
v-if="item.pinned"
:name="item.icon"
class="w-5 h-5 me-2"
/>
{{ item.label }}
</UButton>
</div>
<UDivider class="my-2"/>
<UAccordion
:items="accordionItems"
:multiple="false"
class="mt-2"
<UNavigationMenu
:items="navItems"
orientation="vertical"
:collapsed="props.collapsed"
tooltip
popover
color="neutral"
highlight
highlight-color="primary"
class="w-full"
:ui="{
root: 'w-full',
list: 'space-y-1',
link: 'min-w-0 rounded-lg px-2.5 py-2',
linkLeadingIcon: 'size-5 shrink-0',
linkLabel: 'truncate',
childList: 'ms-0 space-y-1 border-l border-default ps-3',
childLink: 'min-w-0 rounded-lg px-2 py-1.5',
childLinkLabel: 'truncate'
}"
>
<template #default="{ item, open }">
<UButton
variant="ghost"
:color="(item.children?.some(c => route.path.includes(c.to))) ? 'primary' : 'gray'"
:icon="item.icon"
class="w-full"
<template #item-leading="{ item, active }">
<UIcon
v-if="item.icon"
:name="item.icon"
class="size-5 shrink-0"
:class="active ? 'text-primary' : 'text-muted'"
/>
</template>
<template #item-trailing="{ item, active }">
<UBadge
v-if="item.badge && !props.collapsed"
color="primary"
variant="soft"
size="xs"
>
{{ item.label }}
<template #trailing>
<UIcon
name="i-heroicons-chevron-right-20-solid"
class="w-5 h-5 ms-auto transform transition-transform duration-200"
:class="[open && 'rotate-90']"
/>
</template>
</UButton>
{{ item.badge }}
</UBadge>
<UIcon
v-else-if="item.children?.length"
name="i-heroicons-chevron-down-20-solid"
class="size-4 shrink-0 transition-transform"
:class="active ? 'text-primary' : 'text-muted'"
/>
</template>
<template #item="{ item }">
<div class="flex flex-col">
<UButton
v-for="child in item.children"
:key="child.label"
variant="ghost"
:color="child.to === route.path ? 'primary' : 'gray'"
:icon="child.icon"
class="ml-4"
:to="child.to"
:target="child.target"
:disabled="child.disabled"
@click="child.click ? child.click() : null"
>
{{ child.label }}
</UButton>
</div>
</template>
</UAccordion>
<Calculator v-if="showCalculator" v-model="showCalculator"/>
</UNavigationMenu>
</template>

View File

@@ -36,28 +36,30 @@ const setNotificationAsRead = async (notification) => {
</script>
<template>
<UDashboardSlideover v-model="isNotificationsSlideoverOpen" title="Benachrichtigungen">
<NuxtLink
v-for="notification in notifications"
:key="notification.id"
:to="notification.link"
class="p-3 rounded-md hover:bg-gray-50 dark:hover:bg-gray-800/50 cursor-pointer flex items-center gap-3 relative"
@click="setNotificationAsRead(notification)"
>
<UChip color="primary" :show="!notification.read && !notification.readAt" inset>
<UAvatar alt="FEDEO" size="md" />
</UChip>
<USlideover v-model:open="isNotificationsSlideoverOpen" title="Benachrichtigungen" side="right">
<template #body>
<NuxtLink
v-for="notification in notifications"
:key="notification.id"
:to="notification.link"
class="p-3 rounded-md hover:bg-gray-50 dark:hover:bg-gray-800/50 cursor-pointer flex items-center gap-3 relative"
@click="setNotificationAsRead(notification)"
>
<UChip color="primary" :show="!notification.read && !notification.readAt" inset>
<UAvatar alt="FEDEO" 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.title}}</span>
<div class="text-sm flex-1">
<p class="flex items-center justify-between">
<span class="text-gray-900 dark:text-white font-medium">{{notification.title}}</span>
<time :datetime="notification.date || notification.createdAt || notification.created_at" class="text-gray-500 dark:text-gray-400 text-xs" v-text="formatTimeAgo(new Date(notification.createdAt || notification.created_at))" />
</p>
<p class="text-gray-500 dark:text-gray-400">
{{ notification.message }}
</p>
</div>
</NuxtLink>
</UDashboardSlideover>
<time :datetime="notification.date || notification.createdAt || notification.created_at" class="text-gray-500 dark:text-gray-400 text-xs" v-text="formatTimeAgo(new Date(notification.createdAt || notification.created_at))" />
</p>
<p class="text-gray-500 dark:text-gray-400">
{{ notification.message }}
</p>
</div>
</NuxtLink>
</template>
</USlideover>
</template>

View File

@@ -16,28 +16,30 @@ const onLogout = async () => {
</script>
<template>
<UModal v-model="auth.sessionWarningVisible" prevent-close>
<UCard>
<template #header>
<h3 class="text-lg font-semibold">Sitzung läuft bald ab</h3>
</template>
<UModal v-model:open="auth.sessionWarningVisible" prevent-close>
<template #content>
<UCard>
<template #header>
<h3 class="text-lg font-semibold">Sitzung läuft bald ab</h3>
</template>
<p class="text-sm text-gray-600 dark:text-gray-300">
Deine Sitzung endet in
<span class="font-semibold">{{ remainingTimeLabel }}</span>.
Bitte bestätige, um eingeloggt zu bleiben.
</p>
<p class="text-sm text-gray-600 dark:text-gray-300">
Deine Sitzung endet in
<span class="font-semibold">{{ remainingTimeLabel }}</span>.
Bitte bestätige, um eingeloggt zu bleiben.
</p>
<template #footer>
<div class="flex justify-end gap-2">
<UButton variant="outline" color="gray" @click="onLogout">
Abmelden
</UButton>
<UButton color="primary" @click="onRefresh">
Eingeloggt bleiben
</UButton>
</div>
</template>
</UCard>
<template #footer>
<div class="flex justify-end gap-2">
<UButton variant="outline" color="gray" @click="onLogout">
Abmelden
</UButton>
<UButton color="primary" @click="onRefresh">
Eingeloggt bleiben
</UButton>
</div>
</template>
</UCard>
</template>
</UModal>
</template>

View File

@@ -124,7 +124,7 @@ async function onSubmit(event: FormSubmitEvent<any>) {
</script>
<template>
<UModal v-model="isOpen">
<UModal v-model:open="isOpen">
<UCard :ui="{ ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
<template #header>
<div class="flex items-center justify-between">

View File

@@ -61,33 +61,35 @@ setupPage()
<template>
<UModal :fullscreen="props.mode === 'show'">
<EntityShow
v-if="loaded && props.mode === 'show'"
:type="props.type"
:item="item"
@updateNeeded="setupPage"
:key="item"
:in-modal="true"
/>
<EntityEdit
v-else-if="loaded && (props.mode === 'edit' || props.mode === 'create')"
:type="props.type"
:item="item"
:inModal="true"
@return-data="(data) => emit('return-data',data)"
:createQuery="props.createQuery"
:mode="props.mode"
/>
<!-- <EntityList
v-else-if="loaded && props.mode === 'list'"
:type="props.type"
:items="items"
/>-->
<UProgress
v-else
animation="carousel"
class="p-5 mt-10"
/>
<template #content>
<EntityShow
v-if="loaded && props.mode === 'show'"
:type="props.type"
:item="item"
@updateNeeded="setupPage"
:key="item"
:in-modal="true"
/>
<EntityEdit
v-else-if="loaded && (props.mode === 'edit' || props.mode === 'create')"
:type="props.type"
:item="item"
:inModal="true"
@return-data="(data) => emit('return-data',data)"
:createQuery="props.createQuery"
:mode="props.mode"
/>
<!-- <EntityList
v-else-if="loaded && props.mode === 'list'"
:type="props.type"
:items="items"
/>-->
<UProgress
v-else
animation="carousel"
class="p-5 mt-10"
/>
</template>
</UModal>
</template>

View File

@@ -1,27 +1,59 @@
<script setup>
const auth = useAuthStore()
const selectedTenant = ref(auth.user.tenant_id)
const activeTenantName = computed(() => {
return auth.activeTenantData?.name || auth.tenants?.find((tenant) => tenant.id === auth.activeTenant)?.name || 'Mandant waehlen'
})
const tenantInitials = computed(() => {
return activeTenantName.value
.split(' ')
.filter(Boolean)
.slice(0, 2)
.map((part) => part[0]?.toUpperCase() || '')
.join('') || 'M'
})
const tenantItems = computed(() => [
auth.tenants.map((tenant) => ({
label: tenant.name,
icon: tenant.id === auth.activeTenant ? 'i-heroicons-check' : undefined,
disabled: Boolean(tenant.locked),
onSelect: async (event) => {
if (tenant.locked || tenant.id === auth.activeTenant) {
event?.preventDefault?.()
return
}
await auth.switchTenant(tenant.id)
}
}))
])
</script>
<template>
<USelectMenu
:options="auth.tenants"
value-attribute="id"
class="w-40"
@change="auth.switchTenant(selectedTenant)"
v-model="selectedTenant"
:items="tenantItems"
:content="{ align: 'start', side: 'bottom', sideOffset: 6 }"
:ui="{ content: 'w-[var(--reka-dropdown-menu-trigger-width)] max-w-[var(--reka-dropdown-menu-trigger-width)]' }"
class="block w-40"
:avatar="{
alt: activeTenantName,
text: tenantInitials,
loading: 'lazy'
}"
>
<UButton color="gray" variant="ghost" :class="[open && 'bg-gray-50 dark:bg-gray-800']" class="w-full">
<UAvatar :alt="auth.activeTenantData?.name" size="md" />
<span class="truncate text-gray-900 dark:text-white font-semibold">{{auth.tenants.find(i => auth.activeTenant === i.id).name}}</span>
</UButton>
<template #option="{option}">
{{option.name}}
<template #default="{ open }">
<UButton
color="gray"
variant="ghost"
class="w-full min-w-0 max-w-full justify-start gap-2 rounded-lg px-2.5 py-2 text-left"
:class="[open && 'bg-gray-100 dark:bg-gray-800']"
>
<span class="min-w-0 flex-1 truncate font-medium text-gray-900 dark:text-white">
{{ activeTenantName }}
</span>
</UButton>
</template>
</USelectMenu>
</template>

View File

@@ -1,8 +1,6 @@
<script setup>
const { isHelpSlideoverOpen } = useDashboard()
const { isDashboardSearchModalOpen } = useUIState()
const { metaSymbol } = useShortcuts()
const auth = useAuthStore()
const items = computed(() => [
@@ -31,8 +29,8 @@ const items = computed(() => [
<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="auth.user.email" :class="[open && 'bg-gray-50 dark:bg-gray-800']">
<template #default="slotProps">
<UButton color="gray" variant="ghost" class="w-full" :label="auth.user.email" :class="[slotProps?.open && 'bg-gray-50 dark:bg-gray-800']">
<!-- <template #leading>
<UAvatar :alt="auth.user.email" size="xs" />
</template>-->

View File

@@ -67,6 +67,7 @@ const startImport = () => {
<template>
<UModal :fullscreen="false">
<template #content>
<UCard>
<template #header>
Erstelltes Dokument Kopieren
@@ -101,6 +102,7 @@ const startImport = () => {
</template>
</UCard>
</template>
</UModal>
</template>

View File

@@ -24,7 +24,7 @@ setupPage()
<UTable
v-if="openTasks.length > 0"
:rows="openTasks"
:columns="[{key:'name',label:'Name'},{key:'categorie',label:'Kategorie'}]"
:columns="normalizeTableColumns([{key:'name',label:'Name'},{key:'categorie',label:'Kategorie'}])"
@select="(i) => router.push(`/tasks/show/${i.id}`)"
/>
<div v-else>

View File

@@ -53,7 +53,10 @@ const emit = defineEmits(["click"])
<style scoped>
/* FAB Basis */
.fab-base {
@apply rounded-full px-5 py-4 text-lg font-semibold;
border-radius: 9999px;
padding: 1rem 1.25rem;
font-size: 1.125rem;
font-weight: 600;
/* Wenn nur ein Icon vorhanden ist → runder Kreis */
/* Wenn Label + Icon → Extended FAB */
@@ -61,6 +64,12 @@ const emit = defineEmits(["click"])
/* Optional: Auto-Kreisen wenn kein Label */
#fab:not([label]) {
@apply w-14 h-14 p-0 flex items-center justify-center text-2xl;
width: 3.5rem;
height: 3.5rem;
padding: 0;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
}
</style>

View File

@@ -206,16 +206,62 @@ const addVideo = () => {
<style scoped>
/* Toolbar & Buttons */
.toolbar-btn {
@apply p-1.5 rounded text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors text-sm font-medium min-w-[28px] h-[28px] flex items-center justify-center;
padding: 0.375rem;
border-radius: 0.25rem;
color: #4b5563;
transition: background-color 0.2s ease, color 0.2s ease;
font-size: 0.875rem;
font-weight: 500;
min-width: 28px;
height: 28px;
display: inline-flex;
align-items: center;
justify-content: center;
}
.toolbar-btn.is-active {
@apply bg-gray-200 dark:bg-gray-600 text-black dark:text-white shadow-inner;
background: #e5e7eb;
color: #000;
box-shadow: inset 0 2px 4px 0 rgb(0 0 0 / 0.05);
}
.bubble-btn {
@apply px-2 py-1 hover:bg-gray-100 dark:hover:bg-gray-700 rounded transition-colors text-sm min-w-[24px] h-[24px] flex items-center justify-center text-gray-700 dark:text-gray-200;
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
transition: background-color 0.2s ease, color 0.2s ease;
font-size: 0.875rem;
min-width: 24px;
height: 24px;
display: inline-flex;
align-items: center;
justify-content: center;
color: #374151;
}
.bubble-btn.is-active {
@apply bg-gray-200 dark:bg-gray-600 text-black dark:text-white;
background: #e5e7eb;
color: #000;
}
.toolbar-btn:hover,
.bubble-btn:hover {
background: #f3f4f6;
}
:global(.dark) .toolbar-btn {
color: #d1d5db;
}
:global(.dark) .toolbar-btn:hover,
:global(.dark) .bubble-btn:hover {
background: #374151;
}
:global(.dark) .toolbar-btn.is-active,
:global(.dark) .bubble-btn.is-active {
background: #4b5563;
color: #fff;
}
:global(.dark) .bubble-btn {
color: #e5e7eb;
}
/* GLOBAL EDITOR STYLES */
@@ -235,20 +281,48 @@ const addVideo = () => {
/* MENTION */
.wiki-mention {
/* Pill-Shape, grau/neutral statt knallig blau */
@apply bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-200 px-1.5 py-0.5 rounded-md text-sm font-medium no-underline inline-block mx-0.5 align-middle border border-gray-200 dark:border-gray-700;
background: #f3f4f6;
color: #374151;
padding: 0.125rem 0.375rem;
border-radius: 0.375rem;
font-size: 0.875rem;
font-weight: 500;
text-decoration: none;
display: inline-block;
margin-inline: 0.125rem;
vertical-align: middle;
border: 1px solid #e5e7eb;
box-decoration-break: clone;
}
.wiki-mention::before {
@apply text-gray-400 dark:text-gray-500 mr-0.5;
color: #9ca3af;
margin-right: 0.125rem;
}
.wiki-mention:hover {
@apply bg-primary-50 dark:bg-primary-900/30 border-primary-200 dark:border-primary-800 text-primary-700 dark:text-primary-400;
background: #eefbf0;
border-color: #bbf7d0;
color: #15803d;
cursor: pointer;
}
:global(.dark) .wiki-mention {
background: #1f2937;
color: #e5e7eb;
border-color: #374151;
}
:global(.dark) .wiki-mention::before {
color: #6b7280;
}
:global(.dark) .wiki-mention:hover {
background: rgb(20 83 45 / 0.3);
border-color: #166534;
color: #4ade80;
}
/* TABLE */
table { width: 100% !important; border-collapse: collapse; table-layout: fixed; margin: 1rem 0; }
th, td { border: 1px solid #d1d5db; padding: 0.5rem; vertical-align: top; box-sizing: border-box; min-width: 1em; }
@@ -258,7 +332,7 @@ const addVideo = () => {
.column-resize-handle { background-color: #3b82f6; width: 4px; }
/* CODE */
pre { background: #0d1117; color: #c9d1d9; font-family: 'JetBrains Mono', monospace; padding: 0.75rem 1rem; border-radius: 0.5rem; margin: 1rem 0; overflow-x: auto; }
pre { background: #0d1117; color: #c9d1d9; font-family: var(--font-mono); padding: 0.75rem 1rem; border-radius: 0.5rem; margin: 1rem 0; overflow-x: auto; }
code { color: inherit; padding: 0; background: none; font-size: 0.9rem; }
/* IMG */

View File

@@ -98,7 +98,7 @@
</div>
</div>
<UModal v-model="isCreateModalOpen">
<UModal v-model:open="isCreateModalOpen">
<div class="p-5">
<h3 class="font-bold mb-4">Neue Seite</h3>
<form @submit.prevent="createPage">

View File

@@ -0,0 +1,28 @@
type LegacyTableColumn = {
id?: string
key?: string
label?: unknown
header?: unknown
accessorKey?: string
[key: string]: unknown
}
export const normalizeTableColumns = (columns: LegacyTableColumn[] = []) => {
return columns.map((column, index) => {
const accessorKey = typeof column.accessorKey === 'string'
? column.accessorKey
: typeof column.key === 'string'
? column.key
: undefined
const header = column.header ?? column.label ?? accessorKey ?? `column_${index}`
const id = column.id ?? accessorKey ?? (typeof header === 'string' ? header : `column_${index}`)
return {
...column,
id,
accessorKey,
header
}
})
}

View File

@@ -240,26 +240,42 @@ onMounted(() => {
</UCard>
</UContainer>
</div>
<UDashboardLayout class="safearea" v-else>
<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>
<div class="safearea flex min-h-screen w-full flex-col overflow-hidden" v-else>
<!-- <div
class="border-b border-default bg-default px-3 py-2"
style="padding-top: max(env(safe-area-inset-top, 0px), 0.5rem);"
>
<TenantDropdown class="min-w-0 w-full max-w-sm" />
</div>-->
<UDashboardGroup class="flex min-h-0 flex-1 flex-col overflow-hidden">
<UDashboardSidebar
id="sidebar"
collapsible
resizable
:default-size="18"
:min-size="14"
:max-size="24"
class="shrink-0 border-r border-default bg-default"
>
<template #header>
<TenantDropdown class="w-full"/>
</template>
</UDashboardNavbar>
<UDashboardSidebar id="sidebar">
<MainNav/>
<div class="flex-1"/>
<template #footer>
<template #default="{ collapsed }">
<MainNav :collapsed="collapsed" />
</template>
<template #footer="{ collapsed }">
<div class="flex flex-col gap-3">
<div class="flex items-center gap-2">
<UColorModeToggle :class="[collapsed ? 'mx-auto' : 'ml-3']"/>
<UDashboardSidebarCollapse v-if="collapsed" class="mx-auto" />
</div>
<UColorModeToggle class="ml-3"/>
<LabelPrinterButton class="w-full"/>
<div class="flex flex-col gap-1">
@@ -272,10 +288,10 @@ onMounted(() => {
:icon="item.icon"
@click="item.click ? item.click() : null"
>
{{ item.label }}
<span v-if="!collapsed">{{ item.label }}</span>
<template #trailing>
<UBadge v-if="item.badge" color="primary" variant="solid" size="xs">
<UBadge v-if="!collapsed && item.badge" color="primary" variant="solid" size="xs">
{{ item.badge }}
</UBadge>
</template>
@@ -287,19 +303,18 @@ onMounted(() => {
</div>
</template>
</UDashboardSidebar>
</UDashboardPanel>
<UDashboardPage>
<UDashboardPanel grow>
<div class="flex min-w-0 flex-1 flex-col overflow-hidden">
<slot/>
</UDashboardPanel>
</UDashboardPage>
</div>
</UDashboardGroup>
<HelpSlideover/>
<Calculator v-if="calculatorStore.isOpen"/>
</UDashboardLayout>
</div>
</div>
<div

View File

@@ -7,7 +7,7 @@ export default defineNuxtConfig({
}
},
modules: ['@vite-pwa/nuxt','@pinia/nuxt', '@nuxt/ui', "nuxt-editorjs", '@nuxtjs/fontaine', 'nuxt-viewport', '@nuxtjs/leaflet', '@vueuse/nuxt'],
modules: ['@vite-pwa/nuxt','@pinia/nuxt', '@nuxt/ui-pro', "nuxt-editorjs", '@nuxtjs/fontaine', 'nuxt-viewport', '@nuxtjs/leaflet', '@vueuse/nuxt'],
ssr: false,
@@ -15,14 +15,12 @@ export default defineNuxtConfig({
dirs: ['stores']
},
extends: [
'@nuxt/ui-pro'
],
components: [{
path: '~/components'
}],
css: ['~/assets/css/main.css'],
build: {
transpile: ['@vuepic/vue-datepicker','@tiptap/vue-3','@tiptap/extension-code-block-lowlight',
'lowlight',]
@@ -74,10 +72,6 @@ export default defineNuxtConfig({
},
},
ui: {
icons: ['heroicons', 'mdi', 'simple-icons']
},
colorMode: {
preference: 'system'
},

File diff suppressed because it is too large Load Diff

View File

@@ -37,7 +37,8 @@
"@fullcalendar/vue3": "^6.1.10",
"@iconify/json": "^2.2.171",
"@mmote/niimbluelib": "^0.0.1-alpha.29",
"@nuxt/ui-pro": "^1.6.0",
"@nuxt/ui": "^3.3.7",
"@nuxt/ui-pro": "^3.3.7",
"@nuxtjs/fontaine": "^0.4.1",
"@nuxtjs/google-fonts": "^3.1.0",
"@nuxtjs/strapi": "^1.9.3",

View File

@@ -253,7 +253,7 @@ onMounted(loadData)
</template>
<UTable
:columns="columns"
:columns="normalizeTableColumns(columns)"
:rows="periods"
:loading="loading"
:empty-state="{ icon: 'i-heroicons-calculator', label: 'Keine Daten für die USt-Auswertung vorhanden' }"

View File

@@ -195,7 +195,7 @@ setupPage()
</UDashboardToolbar>
<UTable
:rows="filteredRows"
:columns="columns"
:columns="normalizeTableColumns(columns)"
class="w-full"
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"
@select="(i) => router.push(`/accounts/show/${i.id}`)"

View File

@@ -138,7 +138,7 @@ const saldo = computed(() => {
<UTable
v-if="statementallocations"
:rows="renderedAllocations"
:columns="[{key:'amount', label:'Betrag'},{key:'date', label:'Datum'},{key:'partner', label:'Partner'},{key:'description', label:'Beschreibung'}]"
:columns="normalizeTableColumns([{key:'amount', label:'Betrag'},{key:'date', label:'Datum'},{key:'partner', label:'Partner'},{key:'description', label:'Beschreibung'}])"
@select="(i) => selectAllocation(i)"
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Buchungen anzuzeigen' }"
>

View File

@@ -601,7 +601,7 @@ onMounted(() => {
</div>
<PageLeaveGuard :when="isSyncing"/>
<UModal v-model="suggestionsModalOpen" :ui="{ width: 'sm:max-w-6xl' }">
<UModal v-model:open="suggestionsModalOpen" :ui="{ width: 'sm:max-w-6xl' }">
<UCard>
<template #header>
<div class="flex items-center justify-between gap-3">

View File

@@ -2453,7 +2453,7 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
icon="i-heroicons-magnifying-glass"
@click="showProductSelectionModal = true"
/>
<UModal v-model="showProductSelectionModal">
<UModal v-model:open="showProductSelectionModal">
<UCard>
<template #header>
Artikel Auswählen
@@ -2472,11 +2472,11 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
</InputGroup>
<UTable
:rows="selectedProductcategorie !== 'not set' ? products.filter(i => i.productcategories.includes(selectedProductcategorie)) : products.filter(i => i.productcategories.length === 0)"
:columns="[
:columns="normalizeTableColumns([
{key: 'name',label:'Name'},
{key: 'manufacturer',label:'Hersteller'},
{key: 'articleNumber',label:'Artikelnummer'},
]"
])"
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Artikel anzuzeigen' }"
@select=" (i) => {
row.product = i.id
@@ -2525,7 +2525,7 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
icon="i-heroicons-magnifying-glass"
@click="showServiceSelectionModal = true"
/>
<UModal v-model="showServiceSelectionModal">
<UModal v-model:open="showServiceSelectionModal">
<UCard>
<template #header>
Leistung Auswählen
@@ -2544,11 +2544,11 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
</InputGroup>
<UTable
:rows="selectedServicecategorie !== 'not set' ? services.filter(i => i.servicecategories.includes(selectedServicecategorie)) : services.filter(i => i.servicecategories.length === 0)"
:columns="[
:columns="normalizeTableColumns([
{key: 'name',label:'Name'},
{key: 'serviceNumber',label:'Leistungsnummer'},
{key: 'sellingPrice',label:'Verkaufspreis'},
]"
])"
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Leistungen anzuzeigen' }"
@select=" (i) => {
row.service = i.id
@@ -2670,7 +2670,7 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
v-if="row.agriculture"
@click="row.showEditDiesel = true"
/>
<UModal v-model="row.showEdit">
<UModal v-model:open="row.showEdit">
<UCard>
<!-- <template #header>
Zeile bearbeiten
@@ -2842,7 +2842,7 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
</UModal>
<UModal v-model="row.showEditDiesel">
<UModal v-model:open="row.showEditDiesel">
<UCard>
<template #header>
Dieselverbrauch bearbeiten

View File

@@ -58,7 +58,7 @@
<template #item="{item}">
<div style="height: 80vh; overflow-y: scroll">
<UTable
:columns="getColumnsForTab(item.key)"
:columns="normalizeTableColumns(getColumnsForTab(item.key))"
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Belege anzuzeigen' }"
:rows="getRowsForTab(item.key)"
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"

View File

@@ -94,7 +94,7 @@
<UTable
:rows="filteredRows"
:columns="columns"
:columns="normalizeTableColumns(columns)"
class="w-full"
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"
@select="(row) => router.push(`/createDocument/edit/${row.id}`)"
@@ -139,7 +139,7 @@
</template>
</UTable>
<UModal v-model="showExecutionModal" :ui="{ width: 'sm:max-w-4xl' }">
<UModal v-model:open="showExecutionModal" :ui="{ width: 'sm:max-w-4xl' }">
<UCard>
<template #header>
<div class="flex items-center justify-between">
@@ -205,7 +205,7 @@
<UTable
v-model="selectedExecutionRows"
:rows="filteredExecutionList"
:columns="executionColumns"
:columns="normalizeTableColumns(executionColumns)"
:ui="{ th: { base: 'whitespace-nowrap' } }"
>
<template #partner-data="{row}">
@@ -247,7 +247,7 @@
</UCard>
</UModal>
<USlideover v-model="showExecutionsSlideover" :ui="{ width: 'w-screen max-w-md' }">
<USlideover v-model:open="showExecutionsSlideover" :ui="{ width: 'w-screen max-w-md' }">
<UCard class="flex flex-col flex-1" :ui="{ body: { base: 'flex-1' }, ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
<template #header>
<div class="flex items-center justify-between">

View File

@@ -111,14 +111,14 @@ const createExport = async () => {
<UTable
:rows="filteredExports"
:columns="[
:columns="normalizeTableColumns([
{ key: 'created_at', label: 'Erstellt am' },
{ key: 'start_date', label: 'Start' },
{ key: 'end_date', label: 'Ende' },
{ key: 'valid_until', label: 'Gültig bis' },
{ key: 'type', label: 'Typ' },
{ key: 'download', label: 'Download' },
]"
])"
>
<template #created_at-data="{row}">
{{dayjs(row.created_at).format("DD.MM.YYYY HH:mm")}}
@@ -137,7 +137,7 @@ const createExport = async () => {
</template>
</UTable>
<UModal v-model="showCreateExportModal">
<UModal v-model:open="showCreateExportModal">
<UCard>
<template #header>
Export erstellen

View File

@@ -453,7 +453,7 @@ const syncdokubox = async () => {
</div>
</UDashboardPanelContent>
<UModal v-model="createFolderModalOpen">
<UModal v-model:open="createFolderModalOpen">
<UCard>
<template #header><h3 class="font-bold">Ordner erstellen</h3></template>
@@ -494,7 +494,7 @@ const syncdokubox = async () => {
</UCard>
</UModal>
<UModal v-model="renameModalOpen">
<UModal v-model:open="renameModalOpen">
<UCard>
<template #header><h3 class="font-bold">Umbenennen</h3></template>
<UFormGroup label="Neuer Name">

View File

@@ -251,7 +251,7 @@ const selectIncomingInvoice = (invoice) => {
sort-mode="manual"
@update:sort="setupPage"
:rows="filteredRows.filter(i => item.label === 'Gebucht' ? i.state === 'Gebucht' : i.state !== 'Gebucht' )"
:columns="columns"
:columns="normalizeTableColumns(columns)"
class="w-full"
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"
@select="(i) => selectIncomingInvoice(i) "

View File

@@ -382,8 +382,7 @@ onBeforeUnmount(() => {
</template>
</UDashboardNavbar>
<UDashboardPanelContent>
<div v-if="visibleWidgets.length > 0" ref="gridElement" class="grid-stack dashboard-grid">
<div v-if="visibleWidgets.length > 0" ref="gridElement" class="grid-stack dashboard-grid overflow-y-auto">
<div
v-for="widget in visibleWidgets"
:key="widget.id"
@@ -396,9 +395,9 @@ onBeforeUnmount(() => {
:gs-min-w="widget.minW"
:gs-min-h="widget.minH"
>
<div class="grid-stack-item-content dashboard-grid-item">
<div class="dashboard-widget-card border border-gray-200 dark:border-gray-800">
<div class="dashboard-widget-header border-b border-gray-200 dark:border-gray-800">
<div class="grid-stack-item-content dashboard-grid-item">
<div class="dashboard-widget-card border border-gray-200 dark:border-gray-800">
<div class="dashboard-widget-header border-b border-gray-200 dark:border-gray-800">
<div class="flex items-start justify-between gap-3">
<div class="min-w-0">
<div :class="['dashboard-widget-drag-handle font-semibold', isEditMode ? 'cursor-move' : 'cursor-default']">
@@ -449,57 +448,58 @@ onBeforeUnmount(() => {
Karte hinzufügen
</UButton>
</div>
</UDashboardPanelContent>
<UModal v-model="manageCardsOpen">
<UCard>
<template #header>
<div class="flex items-center justify-between gap-3">
<div>
<h2 class="font-semibold">Dashboard-Karten</h2>
<p class="text-sm">
Karten ein- oder ausblenden und bei Bedarf auf das Standardlayout zurücksetzen.
</p>
<UModal v-model:open="manageCardsOpen">
<template #content>
<UCard>
<template #header>
<div class="flex items-center justify-between gap-3">
<div>
<h2 class="font-semibold">Dashboard-Karten</h2>
<p class="text-sm">
Karten ein- oder ausblenden und bei Bedarf auf das Standardlayout zurücksetzen.
</p>
</div>
<UButton color="gray" variant="ghost" icon="i-heroicons-arrow-path" @click="resetDashboard">
Zurücksetzen
</UButton>
</div>
<UButton color="gray" variant="ghost" icon="i-heroicons-arrow-path" @click="resetDashboard">
Zurücksetzen
</UButton>
</div>
</template>
</template>
<div class="space-y-3">
<div
v-for="definition in DASHBOARD_WIDGETS"
:key="definition.id"
class="flex items-center justify-between gap-3 rounded-lg border border-gray-200 dark:border-gray-800 px-4 py-3"
>
<div>
<p class="font-medium">{{ definition.title }}</p>
<p class="text-sm">{{ definition.description }}</p>
<div class="space-y-3">
<div
v-for="definition in DASHBOARD_WIDGETS"
:key="definition.id"
class="flex items-center justify-between gap-3 rounded-lg border border-gray-200 dark:border-gray-800 px-4 py-3"
>
<div>
<p class="font-medium">{{ definition.title }}</p>
<p class="text-sm">{{ definition.description }}</p>
</div>
<UButton
v-if="getWidgetLayout(definition.id)?.visible"
color="gray"
variant="soft"
icon="i-heroicons-minus"
:disabled="visibleWidgets.length <= 1"
@click="removeWidget(definition.id)"
>
Entfernen
</UButton>
<UButton
v-else
color="primary"
variant="soft"
icon="i-heroicons-plus"
@click="addWidget(definition.id)"
>
Hinzufügen
</UButton>
</div>
<UButton
v-if="getWidgetLayout(definition.id)?.visible"
color="gray"
variant="soft"
icon="i-heroicons-minus"
:disabled="visibleWidgets.length <= 1"
@click="removeWidget(definition.id)"
>
Entfernen
</UButton>
<UButton
v-else
color="primary"
variant="soft"
icon="i-heroicons-plus"
@click="addWidget(definition.id)"
>
Hinzufügen
</UButton>
</div>
</div>
</UCard>
</UCard>
</template>
</UModal>
</div>
</template>

View File

@@ -453,7 +453,7 @@ onMounted(() => {
/>
</UDashboardPanelContent>
<UModal v-model="isAbsenceModalOpen">
<UModal v-model:open="isAbsenceModalOpen">
<UCard :ui="{ ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
<template #header>
<div class="flex items-center justify-between">

View File

@@ -247,7 +247,7 @@ const addPhase = () => {
{{ button.label }}
</UButton>
<UModal v-model="openQuickActionModal">
<UModal v-model:open="openQuickActionModal">
<UCard>
<div class="flex items-center justify-between">
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">

View File

@@ -88,7 +88,7 @@ const filteredRows = computed(() => {
<UTable
:rows="filteredRows"
:columns="columns"
:columns="normalizeTableColumns(columns)"
class="w-full"
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"
@select="(i) => router.push(`/projecttypes/show/${i.id}`) "

View File

@@ -105,12 +105,14 @@ const userTableColumns = [
{ key: "tenant_count", label: "Tenants" },
{ key: "is_admin", label: "Admin" },
]
const normalizedUserTableColumns = normalizeTableColumns(userTableColumns)
const tenantTableColumns = [
{ key: "name", label: "Tenant" },
{ key: "short", label: "Kürzel" },
{ key: "user_count", label: "Benutzer" },
]
const normalizedTenantTableColumns = normalizeTableColumns(tenantTableColumns)
const userTableRows = computed(() =>
sortedUsers.value.map((user) => ({
@@ -491,7 +493,7 @@ onMounted(async () => {
<UTable
v-if="!loading"
:rows="userTableRows"
:columns="userTableColumns"
:columns="normalizedUserTableColumns"
@select="selectUser"
/>
@@ -673,7 +675,7 @@ onMounted(async () => {
<UTable
v-if="!loading"
:rows="tenantTableRows"
:columns="tenantTableColumns"
:columns="normalizedTenantTableColumns"
@select="selectTenant"
/>
@@ -740,7 +742,7 @@ onMounted(async () => {
</UTabs>
</UDashboardPanelContent>
<UModal v-model="createUserModalOpen">
<UModal v-model:open="createUserModalOpen">
<UCard>
<template #header>
<div class="text-lg font-semibold">Benutzer anlegen</div>
@@ -797,7 +799,7 @@ onMounted(async () => {
</UCard>
</UModal>
<UModal v-model="createTenantModalOpen">
<UModal v-model:open="createTenantModalOpen">
<UCard>
<template #header>
<div class="text-lg font-semibold">Tenant anlegen</div>

View File

@@ -151,7 +151,7 @@ setupPage()
</template>
</UDashboardNavbar>
<UModal v-model="showReqData">
<UModal v-model:open="showReqData">
<UCard>
<template #header>
Verfügbare Bankkonten
@@ -181,7 +181,7 @@ setupPage()
<UTable
:rows="bankaccounts"
:columns="[
:columns="normalizeTableColumns([
{
key: 'expired',
label: 'Aktiv'
@@ -198,7 +198,7 @@ setupPage()
key: 'balance',
label: 'Saldo'
},
]"
])"
>
<template #expired-data="{row}">
<span v-if="row.expired" class="text-rose-600">Ausgelaufen</span>

View File

@@ -34,10 +34,11 @@ const columns = computed(() => templateColumns.filter((column) => selectedColumn
<UModal
v-model="showEmailAddressModal"
>
<UCard>
<template #header>
E-Mail Adresse
</template>
<template #content>
<UCard>
<template #header>
E-Mail Adresse
</template>
<!-- <UFormGroup
label="E-Mail Adresse:"
>
@@ -57,14 +58,15 @@ const columns = computed(() => templateColumns.filter((column) => selectedColumn
v-model="createEMailType"
/>
</UFormGroup>-->
<template #footer>
<UButton
@click="createAccount"
>
Erstellen
</UButton>
</template>
</UCard>
<template #footer>
<UButton
@click="createAccount"
>
Erstellen
</UButton>
</template>
</UCard>
</template>
</UModal>
<UDashboardNavbar title="E-Mail Konten">
@@ -81,7 +83,7 @@ const columns = computed(() => templateColumns.filter((column) => selectedColumn
</UDashboardNavbar>
<UTable
:rows="items"
:columns="columns"
:columns="normalizeTableColumns(columns)"
class="w-full"
@select="(i) => navigateTo(`/settings/emailaccounts/edit/${i.id}`)"
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"

View File

@@ -164,13 +164,13 @@ const getDocLabel = (type) => {
:loading="loading"
v-model:expand="expand"
:empty-state="{ icon: 'i-heroicons-document-text', label: 'Keine Textvorlagen gefunden' }"
:columns="[
:columns="normalizeTableColumns([
{ key: 'name', label: 'Bezeichnung' },
{ key: 'documentType', label: 'Verwendung' },
{ key: 'pos', label: 'Position' },
{ key: 'default', label: 'Standard' },
{ key: 'actions', label: '' }
]"
])"
>
<template #name-data="{ row }">
<span class="font-medium text-gray-900 dark:text-white">{{ row.name }}</span>
@@ -236,7 +236,7 @@ const getDocLabel = (type) => {
</UTable>
</UDashboardPanelContent>
<UModal v-model="editTemplateModalOpen" :ui="{ width: 'sm:max-w-4xl' }">
<UModal v-model:open="editTemplateModalOpen" :ui="{ width: 'sm:max-w-4xl' }">
<UCard>
<template #header>
<div class="flex justify-between items-center">

View File

@@ -40,7 +40,7 @@
</UDashboardNavbar>
<UTable
:rows="items"
:columns="columns"
:columns="normalizeTableColumns(columns)"
@select="(i) => navigateTo(`/staff/profiles/${i.id}`)"
>

View File

@@ -296,13 +296,13 @@ await setupPage()
v-if="workingTimeInfo"
:rows="workingTimeInfo.spans"
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Anwesenheiten' }"
:columns="[
:columns="normalizeTableColumns([
{ key: 'status', label: 'Status' },
{ key: 'startedAt', label: 'Start' },
{ key: 'endedAt', label: 'Ende' },
{ key: 'duration', label: 'Dauer' },
{ key: 'type', label: 'Typ' }
]"
])"
@select="(row) => router.push(`/workingtimes/edit/${row.sourceEventIds[0]}`)"
>
<template #status-data="{row}">

View File

@@ -228,7 +228,7 @@ onMounted(async () => {
<UCard v-if="view === 'list'" :ui="{ body: { padding: 'p-0 sm:p-0' } }">
<UTable
:rows="entries"
:columns="[
:columns="normalizeTableColumns([
{ key: 'actions', label: 'Aktionen', class: 'w-32' },
{ key: 'state', label: 'Status' },
{ key: 'started_at', label: 'Start' },
@@ -236,7 +236,7 @@ onMounted(async () => {
{ key: 'duration_minutes', label: 'Dauer' },
{ key: 'type', label: 'Typ' },
{ key: 'description', label: 'Beschreibung' },
]"
])"
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Zeiten anzuzeigen' }"
>
<template #state-data="{ row }">
@@ -444,7 +444,7 @@ onMounted(async () => {
:default-user-id="selectedUser"
/>
<UModal v-model="showRejectModal">
<UModal v-model:open="showRejectModal">
<UCard :ui="{ ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
<template #header>
<div class="flex items-center justify-between">

View File

@@ -388,7 +388,7 @@ const handleFilterChange = async (action,column) => {
@update:sort="setupPage"
v-if="dataType && columns && items.length > 0 && !loading"
:rows="items"
:columns="columns"
:columns="normalizeTableColumns(columns)"
class="w-full"
style="height: 85dvh"
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"
@@ -428,7 +428,7 @@ const handleFilterChange = async (action,column) => {
<template #empty>
Keine Einträge in der Spalte {{column.label}}
</template>
<template #default="{open}">
<template #default="slotProps">
<UButton
:disabled="!columnsToFilter[column.key]?.length > 0"
:variant="columnsToFilter[column.key]?.length !== itemsMeta.distinctValues?.[column.key]?.length ? 'outline' : 'solid'"
@@ -436,7 +436,7 @@ const handleFilterChange = async (action,column) => {
>
<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']" />
<UIcon name="i-heroicons-chevron-right-20-solid" class="w-5 h-5 transition-transform text-gray-400 dark:text-gray-500" :class="[slotProps?.open && 'transform rotate-90']" />
</UButton>
</template>

View File

@@ -65,7 +65,7 @@ const addMessage = async () => {
>
+ Eintrag
</UButton>
<UModal v-model="showAddEntryModal">
<UModal v-model:open="showAddEntryModal">
<UCard>
<template #header>
Eintrag hinzufügen

View File

@@ -75,7 +75,7 @@ const filteredRows = computed(() => {
:rows="filteredRows"
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: `Keine Tickets anzuzeigen` }"
@select="(i) => router.push(`/support/${i.id}`)"
:columns="[{key:'created_at',label:'Datum'}, ...profileStore.currentTenant === 5 ? [{key:'tenant',label:'Tenant'}] : [],{key:'status',label:'Status'},{key:'title',label:'Titel'},{key:'created_by',label:'Ersteller'},{key:'ticketmessages',label:'Nachrichten'}]"
:columns="normalizeTableColumns([{key:'created_at',label:'Datum'}, ...profileStore.currentTenant === 5 ? [{key:'tenant',label:'Tenant'}] : [],{key:'status',label:'Status'},{key:'title',label:'Titel'},{key:'created_by',label:'Ersteller'},{key:'ticketmessages',label:'Nachrichten'}])"
>
<template #tenant-data="{ row }">
{{row.tenant.name}}

View File

@@ -88,6 +88,7 @@ const listColumns = [
{ key: "customer", label: "Kunde" },
{ key: "plant", label: "Objekt" }
]
const normalizedListColumns = normalizeTableColumns(listColumns)
function getEmptyTask() {
return {
@@ -458,7 +459,7 @@ onMounted(async () => {
<UTable
v-else-if="filteredTasks.length"
:rows="filteredTasks"
:columns="listColumns"
:columns="normalizedListColumns"
@select="(task) => openTaskViaRoute(task)"
>
<template #actions-data="{ row }">
@@ -497,7 +498,7 @@ onMounted(async () => {
/>
</UDashboardPanelContent>
<UModal v-model="isModalOpen" :prevent-close="saving || deleting">
<UModal v-model:open="isModalOpen" :prevent-close="saving || deleting">
<UCard>
<template #header>
<div class="flex items-center justify-between">

View File

@@ -91,7 +91,7 @@
</div>
</main>
<UModal v-model="isModalOpen">
<UModal v-model:open="isModalOpen">
<UCard :ui="{ ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
<template #header>
<div class="flex items-center justify-between">