352 lines
11 KiB
Vue
352 lines
11 KiB
Vue
<script setup>
|
|
import { ref, onMounted } from 'vue'
|
|
|
|
const dataStore = useDataStore()
|
|
const toast = useToast()
|
|
|
|
// useEntities Initialisierung für 'texttemplates'
|
|
const { select, create, update } = useEntities("texttemplates")
|
|
|
|
// --- State ---
|
|
const editTemplateModalOpen = ref(false)
|
|
const itemInfo = ref({})
|
|
const texttemplates = ref([])
|
|
const loading = ref(true)
|
|
const isSaving = ref(false)
|
|
const textareaRef = ref(null)
|
|
|
|
// Tabelle Expand State
|
|
const expand = ref({
|
|
openedRows: [],
|
|
row: {}
|
|
})
|
|
|
|
// --- Variablen Definitionen ---
|
|
const variableDefinitions = [
|
|
{ key: '{{vorname}}', label: 'Vorname', desc: 'Vorname des Kunden' },
|
|
{ key: '{{nachname}}', label: 'Nachname', desc: 'Nachname des Kunden' },
|
|
{ key: '{{anrede}}', label: 'Anrede', desc: 'Formelle Anrede' },
|
|
{ key: '{{titel}}', label: 'Titel', desc: 'Titel des Kunden' },
|
|
{ key: '{{zahlungsziel_in_tagen}}', label: 'Zahlungsziel', desc: 'In Tagen' },
|
|
{ key: '{{lohnkosten}}', label: 'Lohnkosten', desc: 'Ausgewiesene Lohnkosten' },
|
|
]
|
|
|
|
// --- Shortcuts ---
|
|
defineShortcuts({
|
|
'+': () => openModal()
|
|
})
|
|
|
|
// --- Data Fetching ---
|
|
const refreshData = async () => {
|
|
loading.value = true
|
|
try {
|
|
// select() filtert bereits archivierte Einträge, wenn dataType.isArchivable true ist
|
|
texttemplates.value = await select()
|
|
} catch (e) {
|
|
toast.add({ title: 'Fehler beim Laden', description: e.message, color: 'rose' })
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
// Initialer Load
|
|
onMounted(() => {
|
|
refreshData()
|
|
})
|
|
|
|
// --- Actions ---
|
|
|
|
const openModal = (item = null) => {
|
|
if (item) {
|
|
// Deep Copy um Reaktivitätsprobleme beim Abbrechen zu vermeiden
|
|
itemInfo.value = JSON.parse(JSON.stringify(item))
|
|
} else {
|
|
// Reset für Erstellen
|
|
itemInfo.value = {
|
|
name: '',
|
|
documentType: 'offer', // Default
|
|
pos: 'startText',
|
|
text: '',
|
|
default: false
|
|
}
|
|
}
|
|
editTemplateModalOpen.value = true
|
|
}
|
|
|
|
const insertVariable = (variableKey) => {
|
|
itemInfo.value.text = (itemInfo.value.text || '') + variableKey + ' '
|
|
}
|
|
|
|
const handleCreate = async () => {
|
|
isSaving.value = true
|
|
try {
|
|
// create(payload, noRedirect) -> Wir setzen noRedirect auf true
|
|
await create(itemInfo.value, true)
|
|
|
|
// Hinweis: Erfolgs-Toast kommt bereits aus useEntities
|
|
editTemplateModalOpen.value = false
|
|
await refreshData()
|
|
} catch (e) {
|
|
toast.add({ title: 'Fehler', description: 'Konnte nicht erstellt werden.', color: 'rose' })
|
|
} finally {
|
|
isSaving.value = false
|
|
}
|
|
}
|
|
|
|
const handleUpdate = async () => {
|
|
isSaving.value = true
|
|
try {
|
|
// update(id, payload, noRedirect) -> Wir setzen noRedirect auf true
|
|
await update(itemInfo.value.id, itemInfo.value, true)
|
|
|
|
// Hinweis: Erfolgs-Toast kommt bereits aus useEntities
|
|
editTemplateModalOpen.value = false
|
|
await refreshData()
|
|
} catch (e) {
|
|
toast.add({title: 'Fehler', description: 'Konnte nicht gespeichert werden.', color: 'rose'})
|
|
} finally {
|
|
isSaving.value = false
|
|
}
|
|
}
|
|
|
|
const handleArchive = async (row) => {
|
|
try {
|
|
// Wir nutzen update mit archived: true und noRedirect, um auf der Seite zu bleiben
|
|
await update(row.id, {archived: true}, true)
|
|
|
|
await refreshData()
|
|
} catch (e) {
|
|
toast.add({title: 'Fehler', description: 'Konnte nicht archiviert werden.', color: 'rose'})
|
|
}
|
|
}
|
|
|
|
// Helper für Labels (falls dataStore noch lädt oder Key fehlt)
|
|
const getDocLabel = (type) => {
|
|
return dataStore.documentTypesForCreation?.[type]?.label || type
|
|
}
|
|
|
|
</script>
|
|
|
|
<template>
|
|
<UDashboardNavbar title="Text Vorlagen">
|
|
<template #right>
|
|
<UButton
|
|
icon="i-heroicons-plus"
|
|
@click="openModal()"
|
|
color="primary"
|
|
variant="solid"
|
|
>
|
|
Erstellen
|
|
</UButton>
|
|
</template>
|
|
</UDashboardNavbar>
|
|
|
|
<UDashboardPanelContent>
|
|
|
|
<UAlert
|
|
icon="i-heroicons-information-circle"
|
|
color="primary"
|
|
variant="soft"
|
|
title="Platzhalter nutzen"
|
|
description="Nutzen Sie die Variablen im Editor, um dynamische Inhalte (wie Kundennamen) automatisch einzufügen."
|
|
class="mb-4 mx-5 mt-2"
|
|
/>
|
|
|
|
<UTable
|
|
class="mt-3"
|
|
:rows="texttemplates"
|
|
:loading="loading"
|
|
v-model:expand="expand"
|
|
:empty-state="{ icon: 'i-heroicons-document-text', label: 'Keine Textvorlagen gefunden' }"
|
|
:columns="[
|
|
{ key: 'name', label: 'Bezeichnung' },
|
|
{ key: 'documentType', label: 'Verwendung' },
|
|
{ key: 'pos', label: 'Position' },
|
|
{ key: 'default', label: 'Standard' },
|
|
{ key: 'actions', label: '' }
|
|
]"
|
|
>
|
|
<template #name-data="{ row }">
|
|
<span class="font-medium text-gray-900 dark:text-white">{{ row.name }}</span>
|
|
</template>
|
|
|
|
<template #documentType-data="{ row }">
|
|
<UBadge color="gray" variant="soft">
|
|
{{ getDocLabel(row.documentType) }}
|
|
</UBadge>
|
|
</template>
|
|
|
|
<template #pos-data="{ row }">
|
|
<div class="flex items-center gap-2">
|
|
<UIcon
|
|
:name="row.pos === 'startText' ? 'i-heroicons-bars-arrow-down' : 'i-heroicons-bars-arrow-up'"
|
|
class="w-4 h-4 text-gray-500"
|
|
/>
|
|
<span>{{ row.pos === 'startText' ? 'Einleitung' : 'Endtext' }}</span>
|
|
</div>
|
|
</template>
|
|
|
|
<template #default-data="{ row }">
|
|
<UIcon v-if="row.default" name="i-heroicons-check-circle-20-solid" class="text-green-500"/>
|
|
<span v-else class="text-gray-400">-</span>
|
|
</template>
|
|
|
|
<template #actions-data="{ row }">
|
|
<UButton color="gray" variant="ghost" icon="i-heroicons-pencil-square" @click="openModal(row)"/>
|
|
</template>
|
|
|
|
<template #expand="{ row }">
|
|
<div class="p-6 bg-gray-50 dark:bg-gray-800/50 rounded-b-lg border-t border-gray-200 dark:border-gray-700">
|
|
<div class="mb-4">
|
|
<h4 class="text-sm font-bold uppercase text-gray-500 mb-1">Vorschau</h4>
|
|
<p class="text-gray-800 dark:text-gray-200 whitespace-pre-line p-3 bg-white dark:bg-gray-900 rounded border border-gray-200 dark:border-gray-700 text-sm">
|
|
{{ row.text }}
|
|
</p>
|
|
</div>
|
|
|
|
<div class="flex justify-end gap-3">
|
|
<ButtonWithConfirm
|
|
color="rose"
|
|
variant="soft"
|
|
icon="i-heroicons-archive-box"
|
|
@confirmed="handleArchive(row)"
|
|
>
|
|
<template #button>Archivieren</template>
|
|
<template #header>
|
|
<span class="font-bold">Wirklich archivieren?</span>
|
|
</template>
|
|
Die Vorlage "{{ row.name }}" wird archiviert.
|
|
</ButtonWithConfirm>
|
|
|
|
<UButton
|
|
icon="i-heroicons-pencil"
|
|
@click="openModal(row)"
|
|
>
|
|
Bearbeiten
|
|
</UButton>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</UTable>
|
|
</UDashboardPanelContent>
|
|
|
|
<UModal v-model="editTemplateModalOpen" :ui="{ width: 'sm:max-w-4xl' }">
|
|
<UCard>
|
|
<template #header>
|
|
<div class="flex justify-between items-center">
|
|
<h3 class="text-lg font-semibold">
|
|
{{ itemInfo.id ? 'Vorlage bearbeiten' : 'Neue Vorlage erstellen' }}
|
|
</h3>
|
|
<UButton color="gray" variant="ghost" icon="i-heroicons-x-mark" @click="editTemplateModalOpen = false"/>
|
|
</div>
|
|
</template>
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
|
|
<div class="lg:col-span-2 space-y-4">
|
|
<UFormGroup label="Bezeichnung" required>
|
|
<UInput v-model="itemInfo.name" placeholder="z.B. Standard Angebotstext" icon="i-heroicons-tag"/>
|
|
</UFormGroup>
|
|
|
|
<div class="grid grid-cols-2 gap-4">
|
|
<UFormGroup label="Dokumententyp" required>
|
|
<USelectMenu
|
|
v-model="itemInfo.documentType"
|
|
:options="Object.keys(dataStore.documentTypesForCreation || {})
|
|
.filter(i => i !== 'serialInvoices')
|
|
.map(i => ({ label: dataStore.documentTypesForCreation[i].label, key: i }))"
|
|
option-attribute="label"
|
|
value-attribute="key"
|
|
/>
|
|
</UFormGroup>
|
|
|
|
<UFormGroup label="Position" required>
|
|
<USelectMenu
|
|
v-model="itemInfo.pos"
|
|
:options="[
|
|
{ label: 'Einleitung (Oben)', key: 'startText' },
|
|
{ label: 'Endtext (Unten)', key: 'endText' }
|
|
]"
|
|
option-attribute="label"
|
|
value-attribute="key"
|
|
/>
|
|
</UFormGroup>
|
|
</div>
|
|
|
|
<UFormGroup label="Text Inhalt" required help="Klicken Sie rechts auf eine Variable, um sie einzufügen.">
|
|
<UTextarea
|
|
ref="textareaRef"
|
|
v-model="itemInfo.text"
|
|
:rows="10"
|
|
placeholder="Sehr geehrte Damen und Herren..."
|
|
class="font-mono text-sm"
|
|
/>
|
|
</UFormGroup>
|
|
|
|
<UCheckbox v-model="itemInfo.default" label="Als Standard für diesen Typ verwenden"/>
|
|
</div>
|
|
|
|
<div class="bg-gray-50 dark:bg-gray-800 rounded-lg p-4 border border-gray-200 dark:border-gray-700 h-fit">
|
|
<h4 class="font-semibold mb-3 flex items-center gap-2">
|
|
<UIcon name="i-heroicons-variable"/>
|
|
Variablen
|
|
</h4>
|
|
<p class="text-xs text-gray-500 mb-4">
|
|
Diese Platzhalter werden beim Erstellen des Dokuments ersetzt.
|
|
</p>
|
|
|
|
<div class="flex flex-col gap-2">
|
|
<button
|
|
v-for="v in variableDefinitions"
|
|
:key="v.key"
|
|
@click="insertVariable(v.key)"
|
|
class="group flex items-center justify-between p-2 rounded hover:bg-white dark:hover:bg-gray-700 border border-transparent hover:border-gray-200 dark:hover:border-gray-600 transition-colors text-left"
|
|
type="button"
|
|
>
|
|
<div>
|
|
<code
|
|
class="text-xs font-bold text-primary-600 dark:text-primary-400 bg-primary-50 dark:bg-primary-950/50 px-1 py-0.5 rounded">{{
|
|
v.key
|
|
}}</code>
|
|
<div class="text-xs text-gray-500 mt-0.5">{{ v.desc }}</div>
|
|
</div>
|
|
<UIcon name="i-heroicons-plus-circle" class="w-5 h-5 text-gray-300 group-hover:text-primary-500"/>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<template #footer>
|
|
<div class="flex justify-end gap-3">
|
|
<UButton color="gray" variant="ghost" @click="editTemplateModalOpen = false">
|
|
Abbrechen
|
|
</UButton>
|
|
|
|
<UButton
|
|
v-if="!itemInfo.id"
|
|
color="primary"
|
|
:loading="isSaving"
|
|
@click="handleCreate"
|
|
icon="i-heroicons-plus"
|
|
>
|
|
Erstellen
|
|
</UButton>
|
|
|
|
<UButton
|
|
v-else
|
|
color="primary"
|
|
:loading="isSaving"
|
|
@click="handleUpdate"
|
|
icon="i-heroicons-check"
|
|
>
|
|
Speichern
|
|
</UButton>
|
|
</div>
|
|
</template>
|
|
</UCard>
|
|
</UModal>
|
|
</template>
|
|
|
|
<style scoped>
|
|
</style> |