Restructured Project Rep
This commit is contained in:
54
components/DatePicker.vue
Normal file
54
components/DatePicker.vue
Normal file
@@ -0,0 +1,54 @@
|
||||
<script setup>
|
||||
import { DatePicker as VCalendarDatePicker } from 'v-calendar'
|
||||
import 'v-calendar/dist/style.css'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Date,
|
||||
default: null
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default: "date"
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:model-value', 'close'])
|
||||
|
||||
const colorMode = useColorMode()
|
||||
|
||||
const isDark = computed(() => colorMode.value === 'dark')
|
||||
|
||||
const date = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => {
|
||||
emit('update:model-value', value)
|
||||
emit('close')
|
||||
}
|
||||
})
|
||||
|
||||
const attrs = [{
|
||||
key: 'today',
|
||||
highlight: {
|
||||
fillMode: 'outline',
|
||||
},
|
||||
dates: new Date()
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VCalendarDatePicker
|
||||
show-weeknumbers
|
||||
v-model="date"
|
||||
:mode="props.mode"
|
||||
is24hr
|
||||
transparent
|
||||
borderless
|
||||
color="green"
|
||||
:attributes="attrs"
|
||||
:is-dark="isDark"
|
||||
title-position="left"
|
||||
trim-weeks
|
||||
:first-day-of-week="2"
|
||||
/>
|
||||
</template>
|
||||
348
components/DocumentDisplay.vue
Normal file
348
components/DocumentDisplay.vue
Normal file
@@ -0,0 +1,348 @@
|
||||
<script setup>
|
||||
|
||||
const toast = useToast()
|
||||
const supabase = useSupabaseClient()
|
||||
const dataStore = useDataStore()
|
||||
const router = useRouter()
|
||||
const props = defineProps({
|
||||
documentData: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
openShowModal: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
let {documentData, openShowModal:openShowModalProp } = props;
|
||||
const tags = dataStore.getDocumentTags
|
||||
const openShowModal = ref(false)
|
||||
|
||||
//Functions
|
||||
const openDocument = async () => {
|
||||
//selectedDocument.value = doc
|
||||
openShowModal.value = true
|
||||
}
|
||||
|
||||
const updateDocument = async () => {
|
||||
|
||||
const {url, ...objData} = documentData
|
||||
delete objData.url
|
||||
|
||||
const {data,error} = await supabase
|
||||
.from("documents")
|
||||
.update(objData)
|
||||
.eq('id',objData.id)
|
||||
.select()
|
||||
|
||||
if(error) {
|
||||
console.log(error)
|
||||
} else {
|
||||
toast.add({title: "Dokument aktualisiert"})
|
||||
dataStore.fetchDocuments()
|
||||
openShowModal.value = false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const createVendorInvoice = async () => {
|
||||
const {data:vendorInvoiceData,error:vendorInvoiceError} = await supabase
|
||||
.from("incominginvoices")
|
||||
.insert([{
|
||||
document: documentData.id,
|
||||
tenant: dataStore.currentTenant
|
||||
}])
|
||||
.select()
|
||||
if(vendorInvoiceError) {
|
||||
console.log(vendorInvoiceError)
|
||||
} else if(vendorInvoiceData) {
|
||||
|
||||
const {data:documentUpdateData,error:documentError} = await supabase
|
||||
.from("documents")
|
||||
.update({
|
||||
vendorInvoice: vendorInvoiceData[0].id
|
||||
})
|
||||
.eq('id',documentData.id)
|
||||
.select()
|
||||
|
||||
if(documentError) {
|
||||
console.log(documentError)
|
||||
} else {
|
||||
toast.add({title: "Dokument aktualisiert"})
|
||||
dataStore.fetchDocuments()
|
||||
openShowModal.value = false
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
dataStore.fetchIncomingInvoices()
|
||||
await router.push("/receipts")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
const archiveDocument = () => {
|
||||
documentData.tags.push("Archiviert")
|
||||
updateDocument()
|
||||
}
|
||||
|
||||
const resourceOptions = ref([
|
||||
{label: 'Projekt', value: 'project', optionAttr: "name"},
|
||||
{label: 'Kunde', value: 'customer', optionAttr: "name"},
|
||||
{label: 'Lieferant', value: 'vendor', optionAttr: "name"},
|
||||
{label: 'Fahrzeug', value: 'vehicle', optionAttr: "licensePlate"},
|
||||
{label: 'Objekt', value: 'plant', optionAttr: "name"},
|
||||
{label: 'Produkt', value: 'product', optionAttr: "name"}
|
||||
])
|
||||
const resourceToAssign = ref("project")
|
||||
const itemOptions = ref([])
|
||||
const idToAssign = ref(null)
|
||||
const getItemsBySelectedResource = () => {
|
||||
if(resourceToAssign.value === "project") {
|
||||
itemOptions.value = dataStore.projects
|
||||
} else if(resourceToAssign.value === "customer") {
|
||||
itemOptions.value = dataStore.customers
|
||||
} else if(resourceToAssign.value === "vendor") {
|
||||
itemOptions.value = dataStore.vendors
|
||||
} else if(resourceToAssign.value === "vehicle") {
|
||||
itemOptions.value = dataStore.vehicles
|
||||
} else if(resourceToAssign.value === "product") {
|
||||
itemOptions.value = dataStore.products
|
||||
} else if(resourceToAssign.value === "plant") {
|
||||
itemOptions.value = dataStore.plants
|
||||
} else {
|
||||
itemOptions.value = []
|
||||
}
|
||||
}
|
||||
getItemsBySelectedResource()
|
||||
|
||||
const updateDocumentAssignment = async () => {
|
||||
documentData[resourceToAssign.value] = idToAssign.value
|
||||
await updateDocument()
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="documentListItem">
|
||||
<object
|
||||
:data="documentData.url"
|
||||
class="previewEmbed"
|
||||
type="application/pdf"
|
||||
v-if="!documentData.tags.includes('Bild')"
|
||||
/>
|
||||
<img
|
||||
v-else
|
||||
alt=""
|
||||
:src="documentData.url"
|
||||
/>
|
||||
|
||||
|
||||
|
||||
<UButton
|
||||
@click="openDocument"
|
||||
class="mt-3"
|
||||
>
|
||||
<UIcon name="i-heroicons-eye-solid" />
|
||||
</UButton>
|
||||
<UToggle
|
||||
v-model="documentData.selected"
|
||||
class="ml-2"
|
||||
/>
|
||||
<br>
|
||||
<UBadge
|
||||
v-if="documentData.vendorInvoice"
|
||||
>{{dataStore.incominginvoices.find(item => item.id === documentData.vendorInvoice) ? dataStore.incominginvoices.find(item => item.id === documentData.vendorInvoice).reference : ''}}</UBadge>
|
||||
<UBadge
|
||||
v-if="documentData.inDatev"
|
||||
>DATEV</UBadge>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<USlideover
|
||||
v-model="openShowModal"
|
||||
fullscreen
|
||||
>
|
||||
<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 gap-2">
|
||||
<UBadge
|
||||
v-for="tag in documentData.tags"
|
||||
>
|
||||
{{tag}}
|
||||
</UBadge>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<UContainer class="h-full" :ui="{padding: 'px-1 sm:px-1 lg:px-1'}">
|
||||
<object
|
||||
class="h-full w-full"
|
||||
:data="documentData.url"
|
||||
type="application/pdf"
|
||||
v-if="!documentData.tags.includes('Bild')"
|
||||
/>
|
||||
<img
|
||||
class=" w-full"
|
||||
:src="documentData.url"
|
||||
alt=""
|
||||
v-else
|
||||
/>
|
||||
</UContainer>
|
||||
|
||||
<template #footer>
|
||||
|
||||
<UButtonGroup>
|
||||
<UButton
|
||||
@click="archiveDocument"
|
||||
>
|
||||
Archivieren
|
||||
</UButton>
|
||||
<UButton
|
||||
v-if="documentData.tags.includes('Eingangsrechnung')"
|
||||
@click="createVendorInvoice"
|
||||
>
|
||||
Eingangsrechnung erstellen
|
||||
</UButton>
|
||||
</UButtonGroup>
|
||||
<br>
|
||||
<a
|
||||
:href="documentData.url"
|
||||
target="_blank"
|
||||
>In neuen Tab anzeigen</a>
|
||||
|
||||
<UFormGroup
|
||||
label="Tags ändern:"
|
||||
>
|
||||
<USelectMenu
|
||||
:options="tags"
|
||||
v-model="documentData.tags"
|
||||
@close="updateDocument"
|
||||
multiple
|
||||
>
|
||||
<template #label>
|
||||
{{documentData.tags.length}} ausgewählt
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</UFormGroup>
|
||||
|
||||
<p>Dokument zuweisen:</p>
|
||||
<UFormGroup
|
||||
label="Resource auswählen"
|
||||
>
|
||||
<USelectMenu
|
||||
:options="resourceOptions"
|
||||
v-model="resourceToAssign"
|
||||
value-attribute="value"
|
||||
option-attribute="label"
|
||||
@change="getItemsBySelectedResource"
|
||||
>
|
||||
|
||||
</USelectMenu>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Eintrag auswählen:"
|
||||
>
|
||||
|
||||
</UFormGroup>
|
||||
|
||||
<USelectMenu
|
||||
:options="itemOptions"
|
||||
v-model="idToAssign"
|
||||
:option-attribute="resourceOptions.find(i => i.value === resourceToAssign)? resourceOptions.find(i => i.value === resourceToAssign).optionAttr : 'name'"
|
||||
value-attribute="id"
|
||||
@change="updateDocumentAssignment"
|
||||
></USelectMenu>
|
||||
|
||||
|
||||
<!-- <UFormGroup
|
||||
label="Projekt zuweisen:"
|
||||
>
|
||||
<USelectMenu
|
||||
:options="dataStore.projects"
|
||||
option-attribute="name"
|
||||
value-attribute="id"
|
||||
v-model="documentData.project"
|
||||
@change="updateDocument"
|
||||
searchable
|
||||
:search-attributes="['name']"
|
||||
>
|
||||
<template #label>
|
||||
{{dataStore.projects.find(item => item.id === documentData.project) ? dataStore.projects.find(item => item.id === documentData.project).name : "Kein Projekt ausgewählt" }}
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup
|
||||
label="Kunde zuweisen:"
|
||||
>
|
||||
<USelectMenu
|
||||
:options="dataStore.customers"
|
||||
option-attribute="name"
|
||||
value-attribute="id"
|
||||
v-model="documentData.customer"
|
||||
@change="updateDocument"
|
||||
searchable
|
||||
:search-attributes="['name']"
|
||||
>
|
||||
<template #label>
|
||||
{{dataStore.customers.find(item => item.id === documentData.customer) ? dataStore.customers.find(item => item.id === documentData.customer).name : "Kein Kunde ausgewählt" }}
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</UFormGroup>-->
|
||||
|
||||
|
||||
</template>
|
||||
</UCard>
|
||||
|
||||
|
||||
<!-- <UCard class="h-full">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</UCard>-->
|
||||
</USlideover>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.documentListItem {
|
||||
display:block;
|
||||
width: 15vw;
|
||||
height: 33vh;
|
||||
padding:1em;
|
||||
margin: 0.7em;
|
||||
border: 1px solid lightgrey;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.documentListItem:hover {
|
||||
border: 1px solid #69c350;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.previewEmbed {
|
||||
width: 100%;
|
||||
height: 22vh;
|
||||
overflow: hidden;
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
|
||||
.previewEmbed::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
37
components/DocumentList.vue
Normal file
37
components/DocumentList.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
documents: {
|
||||
type: Array,
|
||||
required:true
|
||||
}
|
||||
})
|
||||
const dataStore = useDataStore()
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="documentList">
|
||||
<DocumentDisplay
|
||||
v-for="item in documents"
|
||||
:document-data="item"
|
||||
:key="item.id"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.documentList {
|
||||
display: flex;
|
||||
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;
|
||||
}
|
||||
</style>
|
||||
111
components/DocumentUpload.vue
Normal file
111
components/DocumentUpload.vue
Normal file
@@ -0,0 +1,111 @@
|
||||
<script setup >
|
||||
|
||||
const props = defineProps({
|
||||
type: {
|
||||
type: String
|
||||
},
|
||||
elementId: {
|
||||
type: String
|
||||
}
|
||||
})
|
||||
|
||||
const {type, elementId} = props
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const tags = dataStore.getDocumentTags
|
||||
|
||||
const uploadModalOpen = ref(false)
|
||||
const uploadInProgress = ref(false)
|
||||
const fileUploadFormData = ref({
|
||||
tags: ["Dokument"],
|
||||
project: null,
|
||||
tenant: dataStore.currentTenant
|
||||
})
|
||||
|
||||
const openModal = () => {
|
||||
console.log("Oepn")
|
||||
uploadModalOpen.value = true
|
||||
}
|
||||
|
||||
const uploadFiles = async () => {
|
||||
uploadInProgress.value = true;
|
||||
|
||||
let fileData = fileUploadFormData.value
|
||||
fileData[type] = elementId
|
||||
|
||||
await dataStore.uploadF632iles(fileData, document.getElementById("fileUploadInput").files,true)
|
||||
|
||||
uploadModalOpen.value = false;
|
||||
uploadInProgress.value = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USlideover
|
||||
v-model="uploadModalOpen"
|
||||
>
|
||||
<UCard :ui="{ 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="uploadModalOpen = false"
|
||||
:disabled="uploadInProgress"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<UFormGroup
|
||||
label="Datei:"
|
||||
>
|
||||
<UInput
|
||||
type="file"
|
||||
id="fileUploadInput"
|
||||
multiple
|
||||
/>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Tags:"
|
||||
class="mt-3"
|
||||
>
|
||||
<USelectMenu
|
||||
multiple
|
||||
searchable
|
||||
searchable-placeholder="Suchen..."
|
||||
:options="tags"
|
||||
v-model="fileUploadFormData.tags"
|
||||
>
|
||||
<template #label>
|
||||
<span v-if="fileUploadFormData.tags.length > 0">{{fileUploadFormData.tags.join(", ")}}</span>
|
||||
<span v-else>Keine Tags ausgewählt</span>
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</UFormGroup>
|
||||
|
||||
<template #footer>
|
||||
<UButton
|
||||
@click="uploadFiles"
|
||||
:loading="uploadInProgress"
|
||||
>Hochladen</UButton>
|
||||
</template>
|
||||
|
||||
</UCard>
|
||||
</USlideover>
|
||||
|
||||
<UButton
|
||||
@click="openModal"
|
||||
icon="i-heroicons-arrow-up-tray"
|
||||
>
|
||||
Hochladen
|
||||
</UButton>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
141
components/Editor.client.vue
Normal file
141
components/Editor.client.vue
Normal file
@@ -0,0 +1,141 @@
|
||||
<script setup>
|
||||
const editor = useEditor({
|
||||
content: "<p>I'm running Tiptap with Vue.js. 🎉</p>",
|
||||
extensions: [TiptapStarterKit],
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
|
||||
<div>
|
||||
<InputGroup>
|
||||
<UButtonGroup>
|
||||
<UButton
|
||||
@click="editor.chain().focus().undo().run()"
|
||||
:disabled="!editor.can().chain().focus().undo().run()"
|
||||
icon="i-mdi-undo"
|
||||
class="px-3"
|
||||
/>
|
||||
<UButton
|
||||
@click="editor.chain().focus().redo().run()"
|
||||
:disabled="!editor.can().chain().focus().redo().run()"
|
||||
icon="i-mdi-redo"
|
||||
class="px-3"
|
||||
/>
|
||||
</UButtonGroup>
|
||||
|
||||
<UButtonGroup v-if="editor">
|
||||
|
||||
|
||||
<UButton
|
||||
@click="editor.chain().focus().toggleBold().run()"
|
||||
:disabled="!editor.can().chain().focus().toggleBold().run()"
|
||||
:variant="editor.isActive('bold') ? 'solid' : 'outline'"
|
||||
>
|
||||
B
|
||||
</UButton>
|
||||
<UButton
|
||||
@click="editor.chain().focus().toggleItalic().run()"
|
||||
:disabled="!editor.can().chain().focus().toggleItalic().run()"
|
||||
:variant="editor.isActive('italic') ? 'solid' : 'outline'"
|
||||
>
|
||||
<span class="italic">I</span>
|
||||
</UButton>
|
||||
<UButton
|
||||
@click="editor.chain().focus().toggleStrike().run()"
|
||||
:disabled="!editor.can().chain().focus().toggleStrike().run()"
|
||||
:variant="editor.isActive('strike') ? 'solid' : 'outline'"
|
||||
>
|
||||
<span class="line-through">D</span>
|
||||
</UButton>
|
||||
</UButtonGroup>
|
||||
<UButtonGroup>
|
||||
<!-- <UButton
|
||||
@click="editor.chain().focus().toggleCode().run()"
|
||||
:disabled="!editor.can().chain().focus().toggleCode().run()"
|
||||
:class="{ 'is-active': editor.isActive('code') }"
|
||||
>
|
||||
code
|
||||
</UButton>
|
||||
<UButton @click="editor.chain().focus().unsetAllMarks().run()">
|
||||
clear marks
|
||||
</UButton>
|
||||
<UButton @click="editor.chain().focus().clearNodes().run()">
|
||||
clear nodes
|
||||
</UButton>
|
||||
<UButton
|
||||
@click="editor.chain().focus().setParagraph().run()"
|
||||
:class="{ 'is-active': editor.isActive('paragraph') }"
|
||||
>
|
||||
<span>P</span>
|
||||
</UButton>-->
|
||||
<UButton
|
||||
@click="editor.chain().focus().toggleHeading({ level: 1 }).run()"
|
||||
:class="{ 'is-active': editor.isActive('heading', { level: 1 }) }"
|
||||
icon="i-mdi-format-header-1"
|
||||
/>
|
||||
<UButton
|
||||
@click="editor.chain().focus().toggleHeading({ level: 2 }).run()"
|
||||
:class="{ 'is-active': editor.isActive('heading', { level: 2 }) }"
|
||||
icon="i-mdi-format-header-2"
|
||||
/>
|
||||
<UButton
|
||||
@click="editor.chain().focus().toggleHeading({ level: 3 }).run()"
|
||||
:class="{ 'is-active': editor.isActive('heading', { level: 3 }) }"
|
||||
icon="i-mdi-format-header-3"
|
||||
/>
|
||||
<UButton
|
||||
@click="editor.chain().focus().toggleHeading({ level: 4 }).run()"
|
||||
:class="{ 'is-active': editor.isActive('heading', { level: 4 }) }"
|
||||
icon="i-mdi-format-header-4"
|
||||
/>
|
||||
<UButton
|
||||
@click="editor.chain().focus().toggleHeading({ level: 5 }).run()"
|
||||
:class="{ 'is-active': editor.isActive('heading', { level: 5 }) }"
|
||||
icon="i-mdi-format-header-5"
|
||||
/>
|
||||
<UButton
|
||||
@click="editor.chain().focus().toggleHeading({ level: 6 }).run()"
|
||||
:class="{ 'is-active': editor.isActive('heading', { level: 6 }) }"
|
||||
icon="i-mdi-format-header-6"
|
||||
/>
|
||||
<UButton
|
||||
@click="editor.chain().focus().toggleBulletList().run()"
|
||||
:class="{ 'is-active': editor.isActive('bulletList') }"
|
||||
icon="i-mdi-format-list-bulleted"
|
||||
/>
|
||||
<UButton
|
||||
@click="editor.chain().focus().toggleOrderedList().run()"
|
||||
:class="{ 'is-active': editor.isActive('orderedList') }"
|
||||
icon="i-mdi-format-list-numbered"
|
||||
/>
|
||||
<!-- <UButton
|
||||
@click="editor.chain().focus().toggleCodeBlock().run()"
|
||||
:class="{ 'is-active': editor.isActive('codeBlock') }"
|
||||
>
|
||||
code block
|
||||
</UButton>
|
||||
<UButton
|
||||
@click="editor.chain().focus().toggleBlockquote().run()"
|
||||
:class="{ 'is-active': editor.isActive('blockquote') }"
|
||||
>
|
||||
blockquote
|
||||
</UButton>
|
||||
<UButton @click="editor.chain().focus().setHorizontalRule().run()">
|
||||
horizontal rule
|
||||
</UButton>
|
||||
<UButton @click="editor.chain().focus().setHardBreak().run()">
|
||||
hard break
|
||||
</UButton>-->
|
||||
|
||||
</UButtonGroup>
|
||||
</InputGroup>
|
||||
|
||||
<TiptapEditorContent class="mt-5" :editor="editor" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
130
components/GlobalSearch.vue
Normal file
130
components/GlobalSearch.vue
Normal file
@@ -0,0 +1,130 @@
|
||||
<script setup>
|
||||
|
||||
const showCommandPalette = ref(false)
|
||||
const selectedCommand = ref("")
|
||||
const commandPaletteRef = ref()
|
||||
const router = useRouter()
|
||||
const dataStore = useDataStore()
|
||||
const openCommandPalette = () => {
|
||||
showCommandPalette.value = true
|
||||
console.log("Open Command Palette")
|
||||
}
|
||||
|
||||
defineShortcuts({
|
||||
meta_k: {
|
||||
usingInput: true,
|
||||
handler: () => {
|
||||
openCommandPalette()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
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" ,
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
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}`}})
|
||||
}
|
||||
].filter(Boolean))
|
||||
|
||||
function onSelect (option) {
|
||||
if (option.click) {
|
||||
option.click()
|
||||
} else if (option.to) {
|
||||
router.push(option.to)
|
||||
} else if (option.href) {
|
||||
window.open(option.href, '_blank')
|
||||
}
|
||||
|
||||
showCommandPalette.value = false
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UButton
|
||||
icon="i-heroicons-magnifying-glass"
|
||||
variant="ghost"
|
||||
@click="openCommandPalette"
|
||||
|
||||
/>
|
||||
|
||||
<UModal
|
||||
v-model="showCommandPalette"
|
||||
>
|
||||
<UCommandPalette
|
||||
v-model="selectedCommand"
|
||||
:groups="groups"
|
||||
:autoselect="false"
|
||||
@update:model-value="onSelect"
|
||||
ref="commandPaletteRef"
|
||||
/>
|
||||
</UModal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
107
components/HelpSlideover.vue
Normal file
107
components/HelpSlideover.vue
Normal 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',
|
||||
onClick: () => {
|
||||
shortcuts.value = true
|
||||
}
|
||||
}, {
|
||||
label: 'Documentation',
|
||||
icon: 'i-heroicons-book-open',
|
||||
to: 'https://fedeo.de',
|
||||
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: 'Hauptmenü' },
|
||||
{ shortcuts: ['N'], name: 'Benachrichtigungen' },
|
||||
{ shortcuts: ['?'], name: 'Help & Support' },
|
||||
{ shortcuts: ['/'], name: 'Suche' }
|
||||
]
|
||||
}, {
|
||||
title: 'Navigation',
|
||||
items: [
|
||||
{ shortcuts: ['G', 'H'], name: 'Gehe zu Dashboard' },
|
||||
{ shortcuts: ['G', 'A'], name: 'Gehe zu Aufgaben' },/*
|
||||
{ shortcuts: ['G', 'I'], name: 'Go to Inbox' },
|
||||
{ shortcuts: ['G', 'U'], name: 'Go to Users' },*/
|
||||
{ shortcuts: ['G', 'S'], name: 'Gehe zu Einstellungen' }
|
||||
]
|
||||
}, /*{
|
||||
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>
|
||||
200
components/HistoryDisplay.vue
Normal file
200
components/HistoryDisplay.vue
Normal file
@@ -0,0 +1,200 @@
|
||||
<script setup>
|
||||
import dayjs from "dayjs"
|
||||
const props = defineProps({
|
||||
type: {
|
||||
required: true,
|
||||
type: String
|
||||
},
|
||||
elementId: {
|
||||
required: true,
|
||||
type: String
|
||||
},
|
||||
renderHeadline: {
|
||||
type: Boolean
|
||||
}
|
||||
})
|
||||
const { metaSymbol } = useShortcuts()
|
||||
const dataStore = useDataStore()
|
||||
const user = useSupabaseUser()
|
||||
const supabase = useSupabaseClient()
|
||||
const toast = useToast()
|
||||
const {type, elementId} = props
|
||||
const showAddHistoryItemModal = ref(false)
|
||||
const colorMode = useColorMode()
|
||||
|
||||
const historyItems = computed(() => {
|
||||
|
||||
let items = []
|
||||
|
||||
if(type === "customer") {
|
||||
items = dataStore.historyItems.filter(i => i.customer === elementId)
|
||||
} else if(type === "vendor") {
|
||||
items = dataStore.historyItems.filter(i => i.vendor === elementId)
|
||||
} else if(type === "project") {
|
||||
items = dataStore.historyItems.filter(i => i.project === elementId)
|
||||
} else if(type === "plant") {
|
||||
items = dataStore.historyItems.filter(i => i.plant === elementId)
|
||||
} else if(type === "incomingInvoice") {
|
||||
items = dataStore.historyItems.filter(i => i.incomingInvoice === elementId)
|
||||
} else if(type === "document") {
|
||||
items = dataStore.historyItems.filter(i => i.document === elementId)
|
||||
} else if(type === "contact") {
|
||||
items = dataStore.historyItems.filter(i => i.contact === elementId)
|
||||
} else if(type === "contract") {
|
||||
items = dataStore.historyItems.filter(i => i.contract === elementId)
|
||||
} else if(type === "inventoryitem") {
|
||||
items = dataStore.historyItems.filter(i => i.inventoryitem === elementId)
|
||||
} else if(type === "product") {
|
||||
items = dataStore.historyItems.filter(i => i.product === elementId)
|
||||
} else if(type === "profile") {
|
||||
items = dataStore.historyItems.filter(i => i.profile === elementId)
|
||||
} else if(type === "absencerequest") {
|
||||
items = dataStore.historyItems.filter(i => i.absenceRequest === elementId)
|
||||
}
|
||||
|
||||
return items
|
||||
|
||||
})
|
||||
const addHistoryItemData = ref({
|
||||
text: "",
|
||||
user: ""
|
||||
})
|
||||
|
||||
const addHistoryItem = async () => {
|
||||
addHistoryItemData.value.user = user.value.id
|
||||
|
||||
if(type === "customer") {
|
||||
addHistoryItemData.value.customer = elementId
|
||||
} else if(type === "vendor") {
|
||||
addHistoryItemData.value.vendor = elementId
|
||||
} else if(type === "project") {
|
||||
addHistoryItemData.value.project = elementId
|
||||
} else if(type === "plant") {
|
||||
addHistoryItemData.value.plant = elementId
|
||||
} else if(type === "incomingInvoice") {
|
||||
addHistoryItemData.value.incomingInvoice = elementId
|
||||
} else if(type === "document") {
|
||||
addHistoryItemData.value.document = elementId
|
||||
} else if(type === "contact") {
|
||||
addHistoryItemData.value.contact = elementId
|
||||
} else if(type === "contract") {
|
||||
addHistoryItemData.value.contract = elementId
|
||||
} else if(type === "inventoryitem") {
|
||||
addHistoryItemData.value.inventoryitem = elementId
|
||||
} else if(type === "product") {
|
||||
addHistoryItemData.value.product = elementId
|
||||
} else if(type === "profile") {
|
||||
addHistoryItemData.value.profile = elementId
|
||||
} else if(type === "absencerequest") {
|
||||
addHistoryItemData.value.absenceRequest = elementId
|
||||
}
|
||||
|
||||
|
||||
const {data,error} = await supabase
|
||||
.from("historyitems")
|
||||
.insert([{...addHistoryItemData.value, tenant: dataStore.currentTenant}])
|
||||
.select()
|
||||
|
||||
if(error) {
|
||||
console.log(error)
|
||||
} else {
|
||||
addHistoryItemData.value = {}
|
||||
toast.add({title: "Eintrag erfolgreich erstellt"})
|
||||
showAddHistoryItemModal.value = false
|
||||
await dataStore.fetchHistoryItems()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const renderText = (text) => {
|
||||
const regex = /(@\w*)/g
|
||||
|
||||
text = text.replaceAll(regex, "<a class='text-primary-500'>$&</a>")
|
||||
|
||||
return text
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UModal
|
||||
v-model="showAddHistoryItemModal"
|
||||
>
|
||||
<UCard>
|
||||
<template #header>
|
||||
Eintrag hinzufügen
|
||||
</template>
|
||||
|
||||
<UFormGroup
|
||||
label="Text:"
|
||||
>
|
||||
<UTextarea
|
||||
v-model="addHistoryItemData.text"
|
||||
@keyup.meta.enter="addHistoryItem"
|
||||
/>
|
||||
<!-- <template #help>
|
||||
<UKbd>{{metaSymbol}}</UKbd> <UKbd>Enter</UKbd> Speichern
|
||||
</template>-->
|
||||
|
||||
</UFormGroup>
|
||||
|
||||
|
||||
<template #footer>
|
||||
<UButton @click="addHistoryItem">Speichern</UButton>
|
||||
</template>
|
||||
</UCard>
|
||||
</UModal>
|
||||
<Toolbar
|
||||
v-if="!renderHeadline"
|
||||
>
|
||||
<UButton
|
||||
@click="showAddHistoryItemModal = true"
|
||||
>
|
||||
+ Eintrag
|
||||
</UButton>
|
||||
</Toolbar>
|
||||
<div v-else>
|
||||
<div :class="`flex justify-between`">
|
||||
<p class="text-2xl">Logbuch</p>
|
||||
<UButton
|
||||
@click="showAddHistoryItemModal = true"
|
||||
>
|
||||
+ Eintrag
|
||||
</UButton>
|
||||
</div>
|
||||
<UDivider class="mt-3"/>
|
||||
</div>
|
||||
|
||||
|
||||
<div
|
||||
v-if="historyItems.length > 0"
|
||||
v-for="(item,index) in historyItems.slice().reverse()"
|
||||
>
|
||||
<UDivider
|
||||
class="my-3"
|
||||
v-if="index !== 0"
|
||||
/>
|
||||
<div class="flex items-center gap-3">
|
||||
<UAvatar
|
||||
v-if="!item.user"
|
||||
:src="colorMode.value === 'light' ? '/Logo.png' : '/Logo_Dark.png' "
|
||||
/>
|
||||
<UAvatar
|
||||
:alt="dataStore.profiles.find(profile => profile.id === item.user).fullName"
|
||||
v-else
|
||||
/>
|
||||
<div>
|
||||
<h3 v-if="item.user">{{dataStore.getProfileById(item.user) ? dataStore.getProfileById(item.user).fullName : ""}}</h3>
|
||||
<h3 v-else>Spaces Bot</h3>
|
||||
<span v-html="renderText(item.text)"/><br>
|
||||
<span class="text-gray-500">{{dayjs(item.created_at).format("DD.MM.YY HH:mm")}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
20
components/InputGroup.vue
Normal file
20
components/InputGroup.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
gap: {
|
||||
type: Number,
|
||||
default: 1
|
||||
}
|
||||
})
|
||||
|
||||
const {gap} = props
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="`flex items-center gap-${gap}`">
|
||||
<slot/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
29
components/NotificationsSlideover.vue
Normal file
29
components/NotificationsSlideover.vue
Normal 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>
|
||||
21
components/Placeholder.vue
Normal file
21
components/Placeholder.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<div class="relative overflow-hidden rounded border border-dashed border-gray-400 dark:border-gray-500 opacity-75 px-4 flex items-center justify-center">
|
||||
<svg class="absolute inset-0 h-full w-full stroke-gray-900/10 dark:stroke-white/10" fill="none">
|
||||
<defs>
|
||||
<pattern
|
||||
id="pattern-5c1e4f0e-62d5-498b-8ff0-cf77bb448c8e"
|
||||
x="0"
|
||||
y="0"
|
||||
width="10"
|
||||
height="10"
|
||||
patternUnits="userSpaceOnUse"
|
||||
>
|
||||
<path d="M-3 13 15-5M-5 5l18-18M-1 21 17 3" />
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect stroke="none" fill="url(#pattern-5c1e4f0e-62d5-498b-8ff0-cf77bb448c8e)" width="100%" height="100%" />
|
||||
</svg>
|
||||
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
28
components/ProfileDropdown.vue
Normal file
28
components/ProfileDropdown.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<script setup>
|
||||
const dataStore = useDataStore()
|
||||
const supabase = useSupabaseClient()
|
||||
//const tenants = ref(dataStore.getOwnProfile ? dataStore.getOwnProfile.tenants : [])
|
||||
//const tenant = ref(dataStore.currentTenant)
|
||||
|
||||
const selectedProfile = ref(dataStore.activeProfile.id)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelectMenu
|
||||
:options="dataStore.ownProfiles"
|
||||
value-attribute="id"
|
||||
class="w-40"
|
||||
@change="dataStore.changeProfile(selectedProfile)"
|
||||
v-model="selectedProfile"
|
||||
>
|
||||
<UButton color="gray" variant="ghost" :class="[open && 'bg-gray-50 dark:bg-gray-800']" class="w-full">
|
||||
<UAvatar :alt="dataStore.tenants.find(i => dataStore.getProfileById(selectedProfile).tenant === i.id).name" size="md" />
|
||||
|
||||
<span class="truncate text-gray-900 dark:text-white font-semibold">{{dataStore.tenants.find(i => dataStore.getProfileById(selectedProfile).tenant === i.id).name}}</span>
|
||||
</UButton>
|
||||
|
||||
<template #option="{option}">
|
||||
{{dataStore.tenants.find(i => i.id === option.tenant).name}}
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</template>
|
||||
24
components/Tiptap.vue
Normal file
24
components/Tiptap.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<editor-content :editor="editor" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useEditor, EditorContent } from '@tiptap/vue-3'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
|
||||
const props = defineProps({
|
||||
content: {
|
||||
type: "String",
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const {content } = props
|
||||
|
||||
const editor = useEditor({
|
||||
content: '<p>I’m running Tiptap with Vue.js. 🎉</p>',
|
||||
extensions: [
|
||||
StarterKit,
|
||||
],
|
||||
})
|
||||
</script>
|
||||
14
components/Toolbar.vue
Normal file
14
components/Toolbar.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<InputGroup>
|
||||
<slot/>
|
||||
</InputGroup>
|
||||
<UDivider class="my-3"/>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
80
components/UserDropdown.vue
Normal file
80
components/UserDropdown.vue
Normal file
@@ -0,0 +1,80 @@
|
||||
<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: 'Suche',
|
||||
icon: 'i-heroicons-command-line',
|
||||
shortcuts: [metaSymbol.value, 'K'],
|
||||
click: () => {
|
||||
isDashboardSearchModalOpen.value = true
|
||||
}
|
||||
}, {
|
||||
label: 'Hilfe & Support',
|
||||
icon: 'i-heroicons-question-mark-circle',
|
||||
shortcuts: ['?'],
|
||||
click: () => isHelpSlideoverOpen.value = true
|
||||
}], [{
|
||||
label: 'Webseite',
|
||||
icon: 'i-heroicons-book-open',
|
||||
to: 'https://fedeo.de',
|
||||
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.activeProfile.fullName" :class="[open && 'bg-gray-50 dark:bg-gray-800']">
|
||||
<template #leading>
|
||||
<UAvatar :alt="dataStore.activeProfile ? dataStore.activeProfile.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.activeProfile.email}}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</UDropdown>
|
||||
</template>
|
||||
64
components/noAutoLoad/BlockEditor.vue
Normal file
64
components/noAutoLoad/BlockEditor.vue
Normal file
@@ -0,0 +1,64 @@
|
||||
<script setup>
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
required: true,
|
||||
type: Object
|
||||
}
|
||||
})
|
||||
|
||||
const {data} = props
|
||||
|
||||
const changesSaved = ref(true)
|
||||
|
||||
|
||||
|
||||
|
||||
const default_data = {
|
||||
time: 1660335428612,
|
||||
blocks: [
|
||||
{
|
||||
id: "MnGi61oxdF",
|
||||
type: "header",
|
||||
data: {
|
||||
text: "Willkommen im Dokumentations Editor",
|
||||
level: 1,
|
||||
},
|
||||
}
|
||||
|
||||
],
|
||||
version: "1.0.0",
|
||||
};
|
||||
const newProjectDescription = ref(data|| default_data.value);
|
||||
|
||||
const saveProjectDescription = async () => {
|
||||
//Update Project Description
|
||||
/*const {data:updateData,error:updateError} = await supabase
|
||||
.from("projects")
|
||||
.update({description: newProjectDescription.value})
|
||||
.eq('id',currentProject.id)
|
||||
.select()
|
||||
|
||||
console.log(updateData)
|
||||
console.log(updateError)*/
|
||||
|
||||
|
||||
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UButton
|
||||
:disabled="false/*newProjectDescription.time === currentProject.description.time*/"
|
||||
@click="saveProjectDescription"
|
||||
>
|
||||
Speichern
|
||||
</UButton>
|
||||
<client-only><EditorJsOwn v-model="newProjectDescription" /></client-only>
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
53
components/noAutoLoad/EditorJsOwn.client.vue
Normal file
53
components/noAutoLoad/EditorJsOwn.client.vue
Normal file
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<div id="editor"></div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import EditorJS from "@editorjs/editorjs";
|
||||
import Header from "@editorjs/header";
|
||||
import List from "@editorjs/list";
|
||||
|
||||
import { onMounted } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
default: {},
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(["update:modelValue"]);
|
||||
onMounted(() => {
|
||||
const editor = new EditorJS({
|
||||
holder: "editor",
|
||||
minHeight: 0,
|
||||
tools: {
|
||||
header: Header,
|
||||
list: List,
|
||||
},
|
||||
onChange: (api, event) => {
|
||||
api.saver.save().then(async (data) => {
|
||||
emit("update:modelValue", data);
|
||||
});
|
||||
},
|
||||
data: props.modelValue,
|
||||
logLevel: "ERROR",
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.codex-editor path {
|
||||
stroke: #69c350;
|
||||
}
|
||||
|
||||
/*.ce-block {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.ce-paragraph, .ce-header {
|
||||
outline: 1px solid #69c350 !important;
|
||||
border-radius: 5px;
|
||||
padding: .5em;
|
||||
|
||||
}*/
|
||||
</style>
|
||||
Reference in New Issue
Block a user