mandant-crm/frontend/components/history/HistoryEntry.vue
Tim Stollberg e5e0be57da feat(frontend): scaffold Nuxt 3 CSR SPA with full auth + client/history UI
- Nuxt 3 CSR (ssr:false), @nuxtjs/tailwindcss, dev proxy to backend
- useAuth composable: CSRF cookie, XSRF token, login/logout/fetchUser
- auth middleware: redirects to /login if no session
- Login page, client list with search + create modal
- Client detail with editable name, dynamic notes (JSON), history feed
- History form with type select, inline edit/delete per entry
- Print layout at /clients/:id/print (calls window.print on mount)
- TypeScript interfaces: User, Client, HistoryEntry, PaginatedResponse
- German UI labels throughout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 17:33:39 +07:00

62 lines
2.1 KiB
Vue

<script setup lang="ts">
import type { HistoryEntry } from '~/types'
const props = defineProps<{ entry: HistoryEntry }>()
const emit = defineEmits<{ delete: [id: number]; update: [id: number, body: string] }>()
const labelMap: Record<string, string> = {
phone_call: 'Telefonat',
consultation: 'Beratungsgespräch',
recommendation: 'Empfehlung',
instruction: 'Anweisung',
remark: 'Bemerkung',
other: 'Sonstiges',
}
const editing = ref(false)
const editBody = ref(props.entry.body)
const saveEdit = () => {
emit('update', props.entry.id, editBody.value)
editing.value = false
}
const formatDate = (iso: string) =>
new Date(iso).toLocaleString('de-DE', { dateStyle: 'short', timeStyle: 'short' })
</script>
<template>
<div class="border border-gray-200 bg-white p-4">
<div class="flex items-start justify-between gap-2 mb-2">
<div class="flex items-center gap-2">
<span class="text-xs font-medium bg-gray-100 text-gray-600 px-2 py-0.5">
{{ labelMap[entry.type] ?? entry.type }}
</span>
<span class="text-xs text-gray-400">{{ formatDate(entry.created_at) }}</span>
<span class="text-xs text-gray-400">{{ entry.author_name }}</span>
<span v-if="entry.updated_by_name" class="text-xs text-gray-400 italic">
(bearb. {{ entry.updated_by_name }})
</span>
</div>
<div class="flex gap-2 shrink-0">
<button @click="editing = !editing" class="text-xs text-blue-600 hover:underline">
{{ editing ? 'Abbrechen' : 'Bearbeiten' }}
</button>
<button @click="emit('delete', entry.id)" class="text-xs text-red-500 hover:underline">
Löschen
</button>
</div>
</div>
<div v-if="!editing" class="text-sm text-gray-800 whitespace-pre-wrap">{{ entry.body }}</div>
<div v-else>
<textarea
v-model="editBody"
rows="4"
class="w-full border border-gray-300 px-3 py-2 text-sm resize-y"
/>
<button @click="saveEdit" class="mt-2 bg-blue-600 text-white px-3 py-1 text-sm hover:bg-blue-700">
Speichern
</button>
</div>
</div>
</template>