Restructured Project Rep

This commit is contained in:
2024-04-07 22:25:16 +02:00
parent 895f508c29
commit d51ad2c4eb
150 changed files with 19358 additions and 7318 deletions

54
components/DatePicker.vue Normal file
View 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>

View 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>

View 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>

View 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>

View 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
View 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>

View File

@@ -0,0 +1,107 @@
<script setup lang="ts">
const { isHelpSlideoverOpen } = useDashboard()
const { metaSymbol } = useShortcuts()
const shortcuts = ref(false)
const query = ref('')
const links = [{
label: 'Shortcuts',
icon: 'i-heroicons-key',
trailingIcon: 'i-heroicons-arrow-right-20-solid',
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>

View 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
View 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>

View File

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

View File

@@ -0,0 +1,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>

View 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
View 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>Im running Tiptap with Vue.js. 🎉</p>',
extensions: [
StarterKit,
],
})
</script>

14
components/Toolbar.vue Normal file
View File

@@ -0,0 +1,14 @@
<script setup lang="ts">
</script>
<template>
<InputGroup>
<slot/>
</InputGroup>
<UDivider class="my-3"/>
</template>
<style scoped>
</style>

View 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>

View 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>

View 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>