|
|
|
|
@@ -1,314 +1,392 @@
|
|
|
|
|
<script setup>
|
|
|
|
|
import { v4 as uuidv4 } from 'uuid'
|
|
|
|
|
|
|
|
|
|
import { v4 as uuidv4 } from 'uuid';
|
|
|
|
|
const defaultPhases = [
|
|
|
|
|
{ icon: 'i-heroicons-clipboard-document', label: 'Erstkontakt' },
|
|
|
|
|
{ icon: 'i-heroicons-wrench-screwdriver', label: 'Umsetzung' },
|
|
|
|
|
{ icon: 'i-heroicons-document-text', label: 'Rechnungsstellung' },
|
|
|
|
|
{ icon: 'i-heroicons-check', label: 'Abgeschlossen' }
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
const quickActionOptions = [
|
|
|
|
|
{ label: '+ Angebot', title: 'Angebot erstellen', link: '/createDocument/edit/?type=quotes' },
|
|
|
|
|
{ label: '+ Kostenschätzung', title: 'Kostenschätzung erstellen', link: '/createDocument/edit/?type=costEstimates' },
|
|
|
|
|
{ label: '+ Auftrag', title: 'Auftrag erstellen', link: '/createDocument/edit/?type=confirmationOrders' },
|
|
|
|
|
{ label: '+ Lieferschein', title: 'Lieferschein erstellen', link: '/createDocument/edit/?type=deliveryNotes' },
|
|
|
|
|
{ label: '+ Packschein', title: 'Packschein erstellen', link: '/createDocument/edit/?type=packingSlips' },
|
|
|
|
|
{ label: '+ Rechnung', title: 'Rechnung erstellen', link: '/createDocument/edit/?type=invoices' },
|
|
|
|
|
{ label: '+ Aufgabe', title: 'Aufgabe erstellen', link: '/tasks?mode=create' },
|
|
|
|
|
{ label: '+ Termin', title: 'Termin erstellen', link: '/standardEntity/events/create' }
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
defineShortcuts({
|
|
|
|
|
'backspace': () => {
|
|
|
|
|
router.push("/projecttypes")
|
|
|
|
|
},
|
|
|
|
|
'arrowleft': () => {
|
|
|
|
|
const currentIndex = Number(openTab.value)
|
|
|
|
|
if(currentIndex > 0){
|
|
|
|
|
openTab.value = String(currentIndex - 1)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
'arrowright': () => {
|
|
|
|
|
const currentIndex = Number(openTab.value)
|
|
|
|
|
if(currentIndex < 3) {
|
|
|
|
|
openTab.value = String(currentIndex + 1)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
const createPhase = (phase = {}, index = -1) => ({
|
|
|
|
|
key: phase.key || uuidv4(),
|
|
|
|
|
icon: phase.icon || '',
|
|
|
|
|
label: phase.label || '',
|
|
|
|
|
optional: Boolean(phase.optional),
|
|
|
|
|
description: phase.description || '',
|
|
|
|
|
quickactions: Array.isArray(phase.quickactions)
|
|
|
|
|
? phase.quickactions.map(action => ({ ...action }))
|
|
|
|
|
: [],
|
|
|
|
|
active: index === 0
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const createEmptyItem = () => ({
|
|
|
|
|
name: '',
|
|
|
|
|
initialPhases: defaultPhases.map(createPhase)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const openTab = ref("0")
|
|
|
|
|
const route = useRoute()
|
|
|
|
|
const router = useRouter()
|
|
|
|
|
const toast = useToast()
|
|
|
|
|
const entities = useEntities('projecttypes')
|
|
|
|
|
|
|
|
|
|
const mode = ref(route.params.mode || "show")
|
|
|
|
|
const itemInfo = ref({
|
|
|
|
|
name: "",
|
|
|
|
|
initialPhases: [{ "key": "f31f6fcb-34d5-41a0-9b8f-6c85062f19be", "icon": "i-heroicons-clipboard-document", "label": "Erstkontakt", "active": true, "quickactions": [] }, { "key": "41995d1f-78fa-448b-b6ea-e206645ffb89", "icon": "i-heroicons-wrench-screwdriver", "label": "Umsetzung", "quickactions": [] }, { "key": "267e78ac-9eab-4736-b9c8-4b94c1724494", "icon": "i-heroicons-document-text", "label": "Rechnungsstellung", "quickactions": [] }, { "key": "038df888-53f2-4985-b08e-776ab82df5d3", "icon": "i-heroicons-check", "label": "Abgeschlossen", "quickactions": [] } ]
|
|
|
|
|
})
|
|
|
|
|
const oldItemInfo = ref({})
|
|
|
|
|
|
|
|
|
|
const mode = computed(() => String(route.params.mode || 'show'))
|
|
|
|
|
const itemInfo = ref(createEmptyItem())
|
|
|
|
|
const loading = ref(false)
|
|
|
|
|
const saving = ref(false)
|
|
|
|
|
const loadError = ref('')
|
|
|
|
|
const openQuickActionModal = ref(false)
|
|
|
|
|
const selectedKeyForQuickAction = ref("")
|
|
|
|
|
const selectedPhaseKey = ref(null)
|
|
|
|
|
const openArchiveModal = ref(false)
|
|
|
|
|
|
|
|
|
|
const setKeys = () => {
|
|
|
|
|
itemInfo.value.initialPhases = itemInfo.value.initialPhases.map(i => {
|
|
|
|
|
return {
|
|
|
|
|
...i,
|
|
|
|
|
key: uuidv4(),
|
|
|
|
|
quickactions: i.quickactions || []
|
|
|
|
|
const isEditMode = computed(() => mode.value === 'edit' || mode.value === 'create')
|
|
|
|
|
const canSave = computed(() => {
|
|
|
|
|
return itemInfo.value.name.trim().length > 0
|
|
|
|
|
&& itemInfo.value.initialPhases.length > 0
|
|
|
|
|
&& itemInfo.value.initialPhases.every(phase => phase.label.trim().length > 0)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const normalizeItem = (item) => ({
|
|
|
|
|
...item,
|
|
|
|
|
name: item?.name || '',
|
|
|
|
|
initialPhases: Array.isArray(item?.initialPhases)
|
|
|
|
|
? item.initialPhases.map(createPhase)
|
|
|
|
|
: []
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const loadItem = async () => {
|
|
|
|
|
loadError.value = ''
|
|
|
|
|
openQuickActionModal.value = false
|
|
|
|
|
openArchiveModal.value = false
|
|
|
|
|
|
|
|
|
|
if (mode.value === 'create') {
|
|
|
|
|
itemInfo.value = createEmptyItem()
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!route.params.id) {
|
|
|
|
|
loadError.value = 'Es wurde kein Projekttyp ausgewählt.'
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
loading.value = true
|
|
|
|
|
try {
|
|
|
|
|
const item = await entities.selectSingle(route.params.id, '*')
|
|
|
|
|
if (!item) {
|
|
|
|
|
loadError.value = 'Der Projekttyp wurde nicht gefunden.'
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
itemInfo.value.initialPhases[0].active = true
|
|
|
|
|
|
|
|
|
|
itemInfo.value = normalizeItem(item)
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Projekttyp konnte nicht geladen werden', error)
|
|
|
|
|
loadError.value = 'Der Projekttyp konnte nicht geladen werden.'
|
|
|
|
|
} finally {
|
|
|
|
|
loading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const setupPage = async() => {
|
|
|
|
|
|
|
|
|
|
if(mode.value === "show" ){
|
|
|
|
|
itemInfo.value = await useEntities("projecttypes").selectSingle(route.params.id,"*")
|
|
|
|
|
} else if (mode.value === "edit") {
|
|
|
|
|
itemInfo.value = await useEntities("projecttypes").selectSingle(route.params.id,"*")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(mode.value === "create") {
|
|
|
|
|
let query = route.query
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
if(itemInfo.value) oldItemInfo.value = JSON.parse(JSON.stringify(itemInfo.value))
|
|
|
|
|
|
|
|
|
|
setKeys()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setupPage()
|
|
|
|
|
|
|
|
|
|
watch(
|
|
|
|
|
() => [route.params.mode, route.params.id],
|
|
|
|
|
loadItem,
|
|
|
|
|
{ immediate: true }
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
defineShortcuts({
|
|
|
|
|
backspace: () => router.push('/projecttypes')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const addPhase = () => {
|
|
|
|
|
itemInfo.value.initialPhases.push({label: '', icon: ''}),
|
|
|
|
|
setKeys
|
|
|
|
|
itemInfo.value.initialPhases.push(createPhase())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const removePhase = (phaseKey) => {
|
|
|
|
|
itemInfo.value.initialPhases = itemInfo.value.initialPhases
|
|
|
|
|
.filter(phase => phase.key !== phaseKey)
|
|
|
|
|
.map(createPhase)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const openQuickActions = (phaseKey) => {
|
|
|
|
|
selectedPhaseKey.value = phaseKey
|
|
|
|
|
openQuickActionModal.value = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const addQuickAction = (action) => {
|
|
|
|
|
const phase = itemInfo.value.initialPhases.find(item => item.key === selectedPhaseKey.value)
|
|
|
|
|
if (!phase) return
|
|
|
|
|
|
|
|
|
|
if (!phase.quickactions.some(item => item.link === action.link)) {
|
|
|
|
|
phase.quickactions.push({ label: action.label, link: action.link })
|
|
|
|
|
}
|
|
|
|
|
openQuickActionModal.value = false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const removeQuickAction = (phase, actionIndex) => {
|
|
|
|
|
phase.quickactions.splice(actionIndex, 1)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const payload = () => ({
|
|
|
|
|
name: itemInfo.value.name.trim(),
|
|
|
|
|
initialPhases: itemInfo.value.initialPhases.map((phase, index) => ({
|
|
|
|
|
...createPhase(phase, index),
|
|
|
|
|
label: phase.label.trim(),
|
|
|
|
|
description: phase.description.trim()
|
|
|
|
|
}))
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const save = async () => {
|
|
|
|
|
if (!canSave.value || saving.value) return
|
|
|
|
|
|
|
|
|
|
saving.value = true
|
|
|
|
|
try {
|
|
|
|
|
if (mode.value === 'create') {
|
|
|
|
|
await entities.create(payload())
|
|
|
|
|
} else {
|
|
|
|
|
await entities.update(itemInfo.value.id, payload())
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Projekttyp konnte nicht gespeichert werden', error)
|
|
|
|
|
toast.add({ title: 'Projekttyp konnte nicht gespeichert werden', color: 'error' })
|
|
|
|
|
} finally {
|
|
|
|
|
saving.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const archiveItem = async () => {
|
|
|
|
|
if (!itemInfo.value.id || saving.value) return
|
|
|
|
|
|
|
|
|
|
saving.value = true
|
|
|
|
|
try {
|
|
|
|
|
await entities.archive(itemInfo.value.id)
|
|
|
|
|
toast.add({ title: 'Projekttyp archiviert' })
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Projekttyp konnte nicht archiviert werden', error)
|
|
|
|
|
toast.add({ title: 'Projekttyp konnte nicht archiviert werden', color: 'error' })
|
|
|
|
|
} finally {
|
|
|
|
|
saving.value = false
|
|
|
|
|
openArchiveModal.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
<UDashboardNavbar :title="itemInfo ? itemInfo.name : (mode === 'create' ? 'Projekt erstellen' : 'Projekt bearbeiten')">
|
|
|
|
|
<UDashboardNavbar :title="itemInfo.name || (mode === 'create' ? 'Projekttyp erstellen' : 'Projekttyp')">
|
|
|
|
|
<template #left>
|
|
|
|
|
<UButton
|
|
|
|
|
icon="i-heroicons-chevron-left"
|
|
|
|
|
variant="outline"
|
|
|
|
|
@click="router.push(`/projecttypes`)"
|
|
|
|
|
>
|
|
|
|
|
<UButton icon="i-heroicons-chevron-left" variant="outline" @click="router.push('/projecttypes')">
|
|
|
|
|
Projekttypen
|
|
|
|
|
</UButton>
|
|
|
|
|
</template>
|
|
|
|
|
<template #center>
|
|
|
|
|
<h1
|
|
|
|
|
v-if="itemInfo"
|
|
|
|
|
class="text-xl font-medium"
|
|
|
|
|
>{{itemInfo.name ? `Projekttyp: ${itemInfo.name}` : (mode === 'create' ? 'Projekttyp erstellen' : 'Projekttyp bearbeiten')}}</h1>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<template #right>
|
|
|
|
|
<UButton
|
|
|
|
|
v-if="mode === 'edit'"
|
|
|
|
|
@click="useEntities('projecttypes').update(itemInfo.id, itemInfo)"
|
|
|
|
|
>
|
|
|
|
|
Speichern
|
|
|
|
|
<UButton v-if="isEditMode" :loading="saving" :disabled="!canSave" @click="save">
|
|
|
|
|
{{ mode === 'create' ? 'Erstellen' : 'Speichern' }}
|
|
|
|
|
</UButton>
|
|
|
|
|
<UButton
|
|
|
|
|
v-else-if="mode === 'create'"
|
|
|
|
|
@click="useEntities('projecttypes').create( itemInfo)"
|
|
|
|
|
>
|
|
|
|
|
Erstellen
|
|
|
|
|
</UButton>
|
|
|
|
|
<UButton
|
|
|
|
|
@click="router.push(itemInfo.id ? `/projecttypes/show/${itemInfo.id}` : `/projecttypes`)"
|
|
|
|
|
color="red"
|
|
|
|
|
class="ml-2"
|
|
|
|
|
v-if="mode === 'edit' || mode === 'create'"
|
|
|
|
|
v-if="isEditMode"
|
|
|
|
|
color="neutral"
|
|
|
|
|
variant="outline"
|
|
|
|
|
@click="router.push(itemInfo.id ? `/projecttypes/show/${itemInfo.id}` : '/projecttypes')"
|
|
|
|
|
>
|
|
|
|
|
Abbrechen
|
|
|
|
|
</UButton>
|
|
|
|
|
<UButton
|
|
|
|
|
v-if="mode === 'show'"
|
|
|
|
|
@click="router.push(`/projecttypes/edit/${itemInfo.id}`)"
|
|
|
|
|
>
|
|
|
|
|
Bearbeiten
|
|
|
|
|
</UButton>
|
|
|
|
|
<template v-if="mode === 'show' && itemInfo.id">
|
|
|
|
|
<UButton variant="outline" @click="router.push(`/projecttypes/edit/${itemInfo.id}`)">
|
|
|
|
|
Bearbeiten
|
|
|
|
|
</UButton>
|
|
|
|
|
<UButton color="error" variant="outline" @click="openArchiveModal = true">
|
|
|
|
|
Archivieren
|
|
|
|
|
</UButton>
|
|
|
|
|
</template>
|
|
|
|
|
</template>
|
|
|
|
|
</UDashboardNavbar>
|
|
|
|
|
|
|
|
|
|
<UDashboardPanelContent>
|
|
|
|
|
<UTabs
|
|
|
|
|
:items="[{label: 'Informationen'}]"
|
|
|
|
|
v-if="itemInfo.id && mode == 'show'"
|
|
|
|
|
v-model="openTab"
|
|
|
|
|
>
|
|
|
|
|
<template #content="{ item }">
|
|
|
|
|
<div v-if="item.label === 'Informationen'" class="flex flex-row">
|
|
|
|
|
<div class="w-1/2 mr-3">
|
|
|
|
|
<UCard class="mt-5">
|
|
|
|
|
{{itemInfo}}
|
|
|
|
|
</UCard>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="w-1/2">
|
|
|
|
|
<UCard class="mt-5">
|
|
|
|
|
<HistoryDisplay
|
|
|
|
|
type="project"
|
|
|
|
|
v-if="itemInfo"
|
|
|
|
|
:element-id="itemInfo.id"
|
|
|
|
|
render-headline
|
|
|
|
|
/>
|
|
|
|
|
</UCard>
|
|
|
|
|
<div v-if="loading" class="flex justify-center py-12">
|
|
|
|
|
<UIcon name="i-heroicons-arrow-path" class="size-7 animate-spin" />
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<UAlert
|
|
|
|
|
v-else-if="loadError"
|
|
|
|
|
color="error"
|
|
|
|
|
variant="soft"
|
|
|
|
|
icon="i-heroicons-exclamation-triangle"
|
|
|
|
|
title="Fehler beim Laden"
|
|
|
|
|
:description="loadError"
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<div v-else-if="mode === 'show'" class="grid gap-5 lg:grid-cols-2">
|
|
|
|
|
<UCard>
|
|
|
|
|
<template #header>
|
|
|
|
|
<h2 class="text-lg font-medium">Phasenvorlage</h2>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<div v-if="itemInfo.initialPhases.length" class="space-y-3">
|
|
|
|
|
<div
|
|
|
|
|
v-for="(phase, index) in itemInfo.initialPhases"
|
|
|
|
|
:key="phase.key"
|
|
|
|
|
class="rounded-lg border border-default p-4"
|
|
|
|
|
>
|
|
|
|
|
<div class="flex items-center gap-3">
|
|
|
|
|
<UIcon :name="phase.icon || 'i-heroicons-clipboard-document'" class="size-5" />
|
|
|
|
|
<span class="font-medium">{{ phase.label }}</span>
|
|
|
|
|
<UBadge v-if="index === 0" variant="soft">Startphase</UBadge>
|
|
|
|
|
<UBadge v-if="phase.optional" color="neutral" variant="soft">Optional</UBadge>
|
|
|
|
|
</div>
|
|
|
|
|
<p v-if="phase.description" class="mt-2 text-sm text-muted">{{ phase.description }}</p>
|
|
|
|
|
<div v-if="phase.quickactions.length" class="mt-3 flex flex-wrap gap-2">
|
|
|
|
|
<UBadge v-for="action in phase.quickactions" :key="action.link" color="neutral" variant="outline">
|
|
|
|
|
{{ action.label }}
|
|
|
|
|
</UBadge>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
</UTabs>
|
|
|
|
|
<TableEmptyState v-else label="Keine Phasen hinterlegt" />
|
|
|
|
|
</UCard>
|
|
|
|
|
|
|
|
|
|
<UForm v-else-if="mode === 'edit' || mode === 'create'">
|
|
|
|
|
<UCard>
|
|
|
|
|
<HistoryDisplay
|
|
|
|
|
v-if="itemInfo.id"
|
|
|
|
|
type="projecttypes"
|
|
|
|
|
:element-id="String(itemInfo.id)"
|
|
|
|
|
render-headline
|
|
|
|
|
/>
|
|
|
|
|
</UCard>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div v-else-if="isEditMode" class="space-y-5">
|
|
|
|
|
<UAlert
|
|
|
|
|
color="error"
|
|
|
|
|
variant="outline"
|
|
|
|
|
class="mb-5"
|
|
|
|
|
v-if="mode === 'edit'"
|
|
|
|
|
description="Achtung Änderungen an diesem Projekttypen betreffen nur Projekte die damit neu erstellt werden. Bestehende Projekte bleiben unverändert."
|
|
|
|
|
v-if="mode === 'edit'"
|
|
|
|
|
color="warning"
|
|
|
|
|
variant="outline"
|
|
|
|
|
description="Änderungen an diesem Projekttyp gelten nur für neu erstellte Projekte. Bestehende Projekte bleiben unverändert."
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<UFormField
|
|
|
|
|
label="Name:"
|
|
|
|
|
>
|
|
|
|
|
<UInput
|
|
|
|
|
v-model="itemInfo.name"
|
|
|
|
|
/>
|
|
|
|
|
</UFormField>
|
|
|
|
|
<UCard>
|
|
|
|
|
<UFormField label="Name" required>
|
|
|
|
|
<UInput v-model="itemInfo.name" class="w-full" placeholder="Name des Projekttyps" />
|
|
|
|
|
</UFormField>
|
|
|
|
|
</UCard>
|
|
|
|
|
|
|
|
|
|
<USeparator class="mt-5">
|
|
|
|
|
Initiale Phasen
|
|
|
|
|
</USeparator>
|
|
|
|
|
<UButton
|
|
|
|
|
class="mt-3"
|
|
|
|
|
@click="addPhase"
|
|
|
|
|
>
|
|
|
|
|
+ Phase
|
|
|
|
|
</UButton>
|
|
|
|
|
<UCard>
|
|
|
|
|
<template #header>
|
|
|
|
|
<div class="flex items-center justify-between gap-3">
|
|
|
|
|
<div>
|
|
|
|
|
<h2 class="text-lg font-medium">Initiale Phasen</h2>
|
|
|
|
|
<p class="text-sm text-muted">Diese Phasen werden in neue Projekte übernommen.</p>
|
|
|
|
|
</div>
|
|
|
|
|
<UButton icon="i-heroicons-plus" @click="addPhase">Phase hinzufügen</UButton>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<table class="mt-3">
|
|
|
|
|
<thead>
|
|
|
|
|
<tr>
|
|
|
|
|
<th></th>
|
|
|
|
|
<th class="text-left"><span class="ml-2">Name</span></th>
|
|
|
|
|
<th class="text-left"><span class="ml-2">Icon</span></th>
|
|
|
|
|
<th class="text-left"><span class="ml-2">Optional</span></th>
|
|
|
|
|
<th class="text-left"><span class="ml-2">Beschreibung</span></th>
|
|
|
|
|
<th class="text-left"><span class="ml-2">Schnellaktionen</span></th>
|
|
|
|
|
<th></th>
|
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
<draggable
|
|
|
|
|
v-model="itemInfo.initialPhases"
|
|
|
|
|
handle=".handle"
|
|
|
|
|
tag="tbody"
|
|
|
|
|
itemKey="pos"
|
|
|
|
|
@end="setKeys"
|
|
|
|
|
v-model="itemInfo.initialPhases"
|
|
|
|
|
handle=".phase-handle"
|
|
|
|
|
item-key="key"
|
|
|
|
|
class="space-y-3"
|
|
|
|
|
>
|
|
|
|
|
<template #item="{ element: phase, index }">
|
|
|
|
|
<div class="rounded-lg border border-default p-4">
|
|
|
|
|
<div class="mb-4 flex items-center gap-2">
|
|
|
|
|
<UIcon name="i-mdi-menu" class="phase-handle size-5 cursor-grab text-muted" />
|
|
|
|
|
<span class="font-medium">Phase {{ index + 1 }}</span>
|
|
|
|
|
<UBadge v-if="index === 0" variant="soft">Startphase</UBadge>
|
|
|
|
|
<UButton
|
|
|
|
|
class="ml-auto"
|
|
|
|
|
icon="i-heroicons-trash"
|
|
|
|
|
color="error"
|
|
|
|
|
variant="ghost"
|
|
|
|
|
:disabled="itemInfo.initialPhases.length === 1"
|
|
|
|
|
aria-label="Phase entfernen"
|
|
|
|
|
@click="removePhase(phase.key)"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<template #item="{element: phase}">
|
|
|
|
|
<tr>
|
|
|
|
|
<td>
|
|
|
|
|
<UIcon
|
|
|
|
|
class="handle"
|
|
|
|
|
name="i-mdi-menu"
|
|
|
|
|
/>
|
|
|
|
|
</td>
|
|
|
|
|
<td>
|
|
|
|
|
<UInput
|
|
|
|
|
class="my-2 ml-2"
|
|
|
|
|
v-model="phase.label"
|
|
|
|
|
placeholder="Name"
|
|
|
|
|
/>
|
|
|
|
|
</td>
|
|
|
|
|
<td>
|
|
|
|
|
<UInput
|
|
|
|
|
class="my-2 ml-2"
|
|
|
|
|
v-model="phase.icon"
|
|
|
|
|
placeholder="Icon"
|
|
|
|
|
/>
|
|
|
|
|
</td>
|
|
|
|
|
<td>
|
|
|
|
|
<UCheckbox
|
|
|
|
|
class="my-2 ml-2"
|
|
|
|
|
v-model="phase.optional"
|
|
|
|
|
/>
|
|
|
|
|
</td>
|
|
|
|
|
<td>
|
|
|
|
|
<UInput
|
|
|
|
|
class="my-2 ml-2"
|
|
|
|
|
v-model="phase.description"
|
|
|
|
|
placeholder="Beschreibung"
|
|
|
|
|
/>
|
|
|
|
|
</td>
|
|
|
|
|
<td>
|
|
|
|
|
<UButton
|
|
|
|
|
class="my-2 ml-2"
|
|
|
|
|
variant="outline"
|
|
|
|
|
@click="openQuickActionModal = true,
|
|
|
|
|
selectedKeyForQuickAction = phase.key"
|
|
|
|
|
>+ Schnellaktion</UButton>
|
|
|
|
|
<UButton
|
|
|
|
|
@click="phase.quickactions = phase.quickactions.filter(i => i.label !== button.label)"
|
|
|
|
|
v-for="button in phase.quickactions"
|
|
|
|
|
class="ml-1"
|
|
|
|
|
>
|
|
|
|
|
{{ button.label }}
|
|
|
|
|
<div class="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
|
|
|
|
|
<UFormField label="Name" required>
|
|
|
|
|
<UInput v-model="phase.label" class="w-full" placeholder="Phasenname" />
|
|
|
|
|
</UFormField>
|
|
|
|
|
<UFormField label="Icon">
|
|
|
|
|
<UInput v-model="phase.icon" class="w-full" placeholder="i-heroicons-…" />
|
|
|
|
|
</UFormField>
|
|
|
|
|
<UFormField label="Beschreibung" class="xl:col-span-2">
|
|
|
|
|
<UInput v-model="phase.description" class="w-full" placeholder="Optionale Beschreibung" />
|
|
|
|
|
</UFormField>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="mt-4 flex flex-wrap items-center gap-2">
|
|
|
|
|
<UCheckbox v-model="phase.optional" label="Optionale Phase" :disabled="index === 0" />
|
|
|
|
|
<UButton class="ml-auto" variant="outline" icon="i-heroicons-bolt" @click="openQuickActions(phase.key)">
|
|
|
|
|
Schnellaktion hinzufügen
|
|
|
|
|
</UButton>
|
|
|
|
|
|
|
|
|
|
<UModal v-model:open="openQuickActionModal">
|
|
|
|
|
<template #content>
|
|
|
|
|
<UCard>
|
|
|
|
|
<div class="flex items-center justify-between">
|
|
|
|
|
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">
|
|
|
|
|
Schnellaktion hinzufügen
|
|
|
|
|
</h3>
|
|
|
|
|
<UButton color="gray" variant="ghost" icon="i-heroicons-x-mark-20-solid" class="-my-1" @click="openQuickActionModal = false" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="flex flex-col">
|
|
|
|
|
<UButton
|
|
|
|
|
class="my-1"
|
|
|
|
|
@click="itemInfo.initialPhases[itemInfo.initialPhases.findIndex(i=> i.key === selectedKeyForQuickAction)].quickactions.push({label:'+ Angebot',link:'/createDocument/edit/?type=quotes'})">Angebot Erstellen</UButton>
|
|
|
|
|
<UButton
|
|
|
|
|
class="my-1"
|
|
|
|
|
@click="itemInfo.initialPhases[itemInfo.initialPhases.findIndex(i=> i.key === selectedKeyForQuickAction)].quickactions.push({label:'+ Kostenschätzung',link:'/createDocument/edit/?type=costEstimates'})">Kostenschätzung Erstellen</UButton>
|
|
|
|
|
<UButton
|
|
|
|
|
class="my-1"
|
|
|
|
|
@click="itemInfo.initialPhases[itemInfo.initialPhases.findIndex(i=> i.key === selectedKeyForQuickAction)].quickactions.push({label:'+ Auftrag',link:'/createDocument/edit/?type=confirmationOrders'})">Auftrag Erstellen</UButton>
|
|
|
|
|
<UButton
|
|
|
|
|
class="my-1"
|
|
|
|
|
@click="itemInfo.initialPhases[itemInfo.initialPhases.findIndex(i=> i.key === selectedKeyForQuickAction)].quickactions.push({label:'+ Lieferschein',link:'/createDocument/edit/?type=deliveryNotes'})">Lieferschein Erstellen</UButton>
|
|
|
|
|
<UButton
|
|
|
|
|
class="my-1"
|
|
|
|
|
@click="itemInfo.initialPhases[itemInfo.initialPhases.findIndex(i=> i.key === selectedKeyForQuickAction)].quickactions.push({label:'+ Packschein',link:'/createDocument/edit/?type=packingSlips'})">Packschein Erstellen</UButton>
|
|
|
|
|
<UButton
|
|
|
|
|
class="my-1"
|
|
|
|
|
@click="itemInfo.initialPhases[itemInfo.initialPhases.findIndex(i=> i.key === selectedKeyForQuickAction)].quickactions.push({label:'+ Rechnung',link:'/createDocument/edit/?type=invoices'})">Rechnung Erstellen</UButton>
|
|
|
|
|
<UButton
|
|
|
|
|
class="my-1"
|
|
|
|
|
@click="itemInfo.initialPhases[itemInfo.initialPhases.findIndex(i=> i.key === selectedKeyForQuickAction)].quickactions.push({label:'+ Aufgabe',link:'/tasks?mode=create'})">Aufgabe Erstellen</UButton>
|
|
|
|
|
<UButton
|
|
|
|
|
class="my-1"
|
|
|
|
|
@click="itemInfo.initialPhases[itemInfo.initialPhases.findIndex(i=> i.key === selectedKeyForQuickAction)].quickactions.push({label:'+ Termin',link:'/standardEntity/events/create'})">Termin Erstellen</UButton>
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
</UCard>
|
|
|
|
|
</template>
|
|
|
|
|
</UModal>
|
|
|
|
|
</td>
|
|
|
|
|
<td>
|
|
|
|
|
<UButton
|
|
|
|
|
class="my-2 ml-2"
|
|
|
|
|
variant="outline"
|
|
|
|
|
color="error"
|
|
|
|
|
@click="itemInfo.initialPhases = itemInfo.initialPhases.filter(i => i !== phase)"
|
|
|
|
|
>X</UButton>
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
v-for="(action, actionIndex) in phase.quickactions"
|
|
|
|
|
:key="`${action.link}-${actionIndex}`"
|
|
|
|
|
color="neutral"
|
|
|
|
|
variant="soft"
|
|
|
|
|
trailing-icon="i-heroicons-x-mark"
|
|
|
|
|
@click="removeQuickAction(phase, actionIndex)"
|
|
|
|
|
>
|
|
|
|
|
{{ action.label }}
|
|
|
|
|
</UButton>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
</draggable>
|
|
|
|
|
</table>
|
|
|
|
|
|
|
|
|
|
</UForm>
|
|
|
|
|
<p v-if="!canSave" class="mt-4 text-sm text-error">
|
|
|
|
|
Name und Phasennamen müssen ausgefüllt sein; mindestens eine Phase ist erforderlich.
|
|
|
|
|
</p>
|
|
|
|
|
</UCard>
|
|
|
|
|
</div>
|
|
|
|
|
</UDashboardPanelContent>
|
|
|
|
|
|
|
|
|
|
<UModal v-model:open="openQuickActionModal" title="Schnellaktion hinzufügen">
|
|
|
|
|
<template #content>
|
|
|
|
|
<UCard>
|
|
|
|
|
<template #header>
|
|
|
|
|
<div class="flex items-center justify-between">
|
|
|
|
|
<h3 class="font-semibold">Schnellaktion hinzufügen</h3>
|
|
|
|
|
<UButton icon="i-heroicons-x-mark" color="neutral" variant="ghost" @click="openQuickActionModal = false" />
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
<div class="grid gap-2 sm:grid-cols-2">
|
|
|
|
|
<UButton
|
|
|
|
|
v-for="action in quickActionOptions"
|
|
|
|
|
:key="action.link"
|
|
|
|
|
variant="outline"
|
|
|
|
|
@click="addQuickAction(action)"
|
|
|
|
|
>
|
|
|
|
|
{{ action.title }}
|
|
|
|
|
</UButton>
|
|
|
|
|
</div>
|
|
|
|
|
</UCard>
|
|
|
|
|
</template>
|
|
|
|
|
</UModal>
|
|
|
|
|
|
|
|
|
|
<UModal v-model:open="openArchiveModal" title="Projekttyp archivieren">
|
|
|
|
|
<template #content>
|
|
|
|
|
<UCard>
|
|
|
|
|
<template #header>
|
|
|
|
|
<h3 class="font-semibold">Projekttyp archivieren?</h3>
|
|
|
|
|
</template>
|
|
|
|
|
<p>„{{ itemInfo.name }}“ wird aus der Projekttyp-Auswahl ausgeblendet.</p>
|
|
|
|
|
<template #footer>
|
|
|
|
|
<div class="flex justify-end gap-2">
|
|
|
|
|
<UButton color="neutral" variant="outline" @click="openArchiveModal = false">Abbrechen</UButton>
|
|
|
|
|
<UButton color="error" :loading="saving" @click="archiveItem">Archivieren</UButton>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
</UCard>
|
|
|
|
|
</template>
|
|
|
|
|
</UModal>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
|
|
|
|
</style>
|
|
|
|
|
|