Projekttyp-Verwaltung reparieren
All checks were successful
Build and Push Docker Images / build-backend (push) Successful in 46s
Build and Push Docker Images / build-frontend (push) Successful in 1m12s
Build and Push Docker Images / build-website (push) Successful in 23s
Build and Push Docker Images / build-central-services-api (push) Successful in 22s
Build and Push Docker Images / build-central-services-admin (push) Successful in 21s
Build and Push Docker Images / build-docs (push) Successful in 21s
All checks were successful
Build and Push Docker Images / build-backend (push) Successful in 46s
Build and Push Docker Images / build-frontend (push) Successful in 1m12s
Build and Push Docker Images / build-website (push) Successful in 23s
Build and Push Docker Images / build-central-services-api (push) Successful in 22s
Build and Push Docker Images / build-central-services-admin (push) Successful in 21s
Build and Push Docker Images / build-docs (push) Successful in 21s
This commit is contained in:
@@ -28,6 +28,7 @@ const columnMap: Record<string, any> = {
|
||||
customerinventoryitems: historyitems.customerinventoryitem,
|
||||
memberrelations: historyitems.memberrelation,
|
||||
outgoingsepamandates: historyitems.outgoingsepamandate,
|
||||
projecttypes: historyitems.projecttype,
|
||||
};
|
||||
|
||||
const insertFieldMap: Record<string, string> = {
|
||||
@@ -55,6 +56,7 @@ const insertFieldMap: Record<string, string> = {
|
||||
customerinventoryitems: "customerinventoryitem",
|
||||
memberrelations: "memberrelation",
|
||||
outgoingsepamandates: "outgoingsepamandate",
|
||||
projecttypes: "projecttype",
|
||||
}
|
||||
|
||||
const parseId = (value: string) => {
|
||||
|
||||
@@ -35,6 +35,7 @@ const HISTORY_ENTITY_LABELS: Record<string, string> = {
|
||||
memberrelations: "Mitgliedsverhältnisse",
|
||||
teams: "Teams",
|
||||
outgoingsepamandates: "Ausgehende SEPA-Mandate",
|
||||
projecttypes: "Projekttypen",
|
||||
}
|
||||
|
||||
export function getHistoryEntityLabel(entity: string) {
|
||||
@@ -96,6 +97,7 @@ export async function insertHistoryItem(
|
||||
files: "file",
|
||||
memberrelations: "memberrelation",
|
||||
outgoingsepamandates: "outgoingsepamandate",
|
||||
projecttypes: "projecttype",
|
||||
}
|
||||
|
||||
const fkColumn = columnMap[params.entity]
|
||||
|
||||
@@ -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
|
||||
}
|
||||
})
|
||||
|
||||
itemInfo.value.initialPhases[0].active = true
|
||||
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 = 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}`)"
|
||||
>
|
||||
<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 v-if="loading" class="flex justify-center py-12">
|
||||
<UIcon name="i-heroicons-arrow-path" class="size-7 animate-spin" />
|
||||
</div>
|
||||
<div class="w-1/2">
|
||||
<UCard class="mt-5">
|
||||
|
||||
<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>
|
||||
<TableEmptyState v-else label="Keine Phasen hinterlegt" />
|
||||
</UCard>
|
||||
|
||||
<UCard>
|
||||
<HistoryDisplay
|
||||
type="project"
|
||||
v-if="itemInfo"
|
||||
:element-id="itemInfo.id"
|
||||
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
|
||||
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."
|
||||
/>
|
||||
|
||||
<UCard>
|
||||
<UFormField label="Name" required>
|
||||
<UInput v-model="itemInfo.name" class="w-full" placeholder="Name des Projekttyps" />
|
||||
</UFormField>
|
||||
</UCard>
|
||||
|
||||
<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>
|
||||
</UTabs>
|
||||
|
||||
<UForm v-else-if="mode === 'edit' || mode === 'create'">
|
||||
<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."
|
||||
/>
|
||||
|
||||
<UFormField
|
||||
label="Name:"
|
||||
>
|
||||
<UInput
|
||||
v-model="itemInfo.name"
|
||||
/>
|
||||
</UFormField>
|
||||
|
||||
<USeparator class="mt-5">
|
||||
Initiale Phasen
|
||||
</USeparator>
|
||||
<UButton
|
||||
class="mt-3"
|
||||
@click="addPhase"
|
||||
>
|
||||
+ Phase
|
||||
</UButton>
|
||||
|
||||
<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"
|
||||
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>
|
||||
<UButton
|
||||
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>
|
||||
|
||||
<UModal v-model:open="openQuickActionModal">
|
||||
<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="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" />
|
||||
<h3 class="font-semibold">Schnellaktion hinzufügen</h3>
|
||||
<UButton icon="i-heroicons-x-mark" color="neutral" variant="ghost" @click="openQuickActionModal = false" />
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
</template>
|
||||
<div class="grid gap-2 sm:grid-cols-2">
|
||||
<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>
|
||||
|
||||
v-for="action in quickActionOptions"
|
||||
:key="action.link"
|
||||
variant="outline"
|
||||
@click="addQuickAction(action)"
|
||||
>
|
||||
{{ action.title }}
|
||||
</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>
|
||||
|
||||
<UModal v-model:open="openArchiveModal" title="Projekttyp archivieren">
|
||||
<template #content>
|
||||
<UCard>
|
||||
<template #header>
|
||||
<h3 class="font-semibold">Projekttyp archivieren?</h3>
|
||||
</template>
|
||||
</draggable>
|
||||
</table>
|
||||
|
||||
</UForm>
|
||||
</UDashboardPanelContent>
|
||||
|
||||
|
||||
<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>
|
||||
|
||||
@@ -3,17 +3,16 @@
|
||||
|
||||
defineShortcuts({
|
||||
'/': () => {
|
||||
//console.log(searchinput)
|
||||
//searchinput.value.focus()
|
||||
document.getElementById("searchinput").focus()
|
||||
document.getElementById("searchinput")?.focus()
|
||||
},
|
||||
'+': () => {
|
||||
router.push("/projects/create")
|
||||
router.push("/projecttypes/create")
|
||||
},
|
||||
'Enter': {
|
||||
usingInput: true,
|
||||
handler: () => {
|
||||
router.push(`/projecttypes/show/${filteredRows.value[selectedItem.value].id}`)
|
||||
const selected = filteredRows.value[selectedItem.value]
|
||||
if (selected) router.push(`/projecttypes/show/${selected.id}`)
|
||||
}
|
||||
},
|
||||
'arrowdown': () => {
|
||||
@@ -38,10 +37,21 @@ const tempStore = useTempStore()
|
||||
|
||||
const items = ref([])
|
||||
const selectedItem = ref(0)
|
||||
const loading = ref(false)
|
||||
const loadError = ref('')
|
||||
|
||||
|
||||
const setup = async () => {
|
||||
items.value = await useEntities("projecttypes").select()
|
||||
loading.value = true
|
||||
loadError.value = ''
|
||||
try {
|
||||
items.value = await useEntities("projecttypes").select('*', 'name', true)
|
||||
} catch (error) {
|
||||
console.error('Projekttypen konnten nicht geladen werden', error)
|
||||
loadError.value = 'Die Projekttypen konnten nicht geladen werden.'
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
setup()
|
||||
@@ -62,6 +72,11 @@ const filteredRows = computed(() => {
|
||||
return useListFilter(searchString.value, items.value)
|
||||
})
|
||||
|
||||
const openProjecttype = (row) => {
|
||||
const item = row?.original || row
|
||||
if (item?.id) router.push(`/projecttypes/show/${item.id}`)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -87,11 +102,13 @@ const filteredRows = computed(() => {
|
||||
</UDashboardNavbar>
|
||||
|
||||
<UTable
|
||||
v-if="!loadError"
|
||||
:data="filteredRows"
|
||||
:columns="normalizeTableColumns(columns)"
|
||||
class="w-full"
|
||||
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"
|
||||
:on-select="(i) => router.push(`/projecttypes/show/${i.id}`) "
|
||||
:loading="loading"
|
||||
:on-select="openProjecttype"
|
||||
>
|
||||
<template #name-cell="{row}">
|
||||
<span class="text-primary-500 font-bold" v-if="row.original === filteredRows[selectedItem]">{{ row.original.name }}</span>
|
||||
@@ -101,6 +118,15 @@ const filteredRows = computed(() => {
|
||||
<TableEmptyState label="Keine Projekttypen anzuzeigen" />
|
||||
</template>
|
||||
</UTable>
|
||||
<UAlert
|
||||
v-else
|
||||
class="m-5"
|
||||
color="error"
|
||||
variant="soft"
|
||||
icon="i-heroicons-exclamation-triangle"
|
||||
title="Fehler beim Laden"
|
||||
:description="loadError"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
Reference in New Issue
Block a user