- 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>
73 lines
2.3 KiB
Vue
73 lines
2.3 KiB
Vue
<script setup lang="ts">
|
|
import type { Client, HistoryEntry } from '~/types'
|
|
|
|
definePageMeta({ middleware: 'auth' })
|
|
|
|
const route = useRoute()
|
|
const clientId = Number(route.params.id)
|
|
|
|
const { fetchClient } = useClient()
|
|
const { entries, fetchEntries } = useHistory(clientId)
|
|
|
|
const client = ref<Client | null>(null)
|
|
client.value = await fetchClient(clientId)
|
|
await fetchEntries()
|
|
|
|
const labelMap: Record<string, string> = {
|
|
phone_call: 'Telefonat',
|
|
consultation: 'Beratungsgespräch',
|
|
recommendation: 'Empfehlung',
|
|
instruction: 'Anweisung',
|
|
remark: 'Bemerkung',
|
|
other: 'Sonstiges',
|
|
}
|
|
|
|
const formatDate = (iso: string) =>
|
|
new Date(iso).toLocaleString('de-DE', { dateStyle: 'short', timeStyle: 'short' })
|
|
|
|
onMounted(() => {
|
|
window.print()
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div v-if="client" class="print-page p-8 max-w-3xl mx-auto font-sans text-sm text-gray-900">
|
|
<h1 class="text-lg font-bold mb-1">Mandantenakte</h1>
|
|
<p class="text-gray-500 mb-6 text-xs">Gedruckt: {{ new Date().toLocaleString('de-DE') }}</p>
|
|
|
|
<table class="w-full mb-6 border-collapse text-sm">
|
|
<tr class="border-b border-gray-200">
|
|
<td class="py-1 text-gray-500 w-40">Mandantennummer</td>
|
|
<td class="py-1 font-mono">{{ client.client_number }}</td>
|
|
</tr>
|
|
<tr class="border-b border-gray-200">
|
|
<td class="py-1 text-gray-500">Name</td>
|
|
<td class="py-1">{{ client.name }}</td>
|
|
</tr>
|
|
<template v-for="(val, key) in client.notes" :key="key">
|
|
<tr class="border-b border-gray-200">
|
|
<td class="py-1 text-gray-500">{{ key }}</td>
|
|
<td class="py-1">{{ val }}</td>
|
|
</tr>
|
|
</template>
|
|
</table>
|
|
|
|
<h2 class="font-semibold mb-3">Verlauf</h2>
|
|
<div v-for="entry in entries" :key="entry.id" class="mb-4 border-t border-gray-200 pt-3">
|
|
<div class="flex gap-3 text-xs text-gray-500 mb-1">
|
|
<span class="font-medium text-gray-700">{{ labelMap[entry.type] ?? entry.type }}</span>
|
|
<span>{{ formatDate(entry.created_at) }}</span>
|
|
<span>{{ entry.author_name }}</span>
|
|
</div>
|
|
<p class="whitespace-pre-wrap text-gray-800">{{ entry.body }}</p>
|
|
</div>
|
|
<p v-if="entries.length === 0" class="text-gray-400 italic">Keine Einträge.</p>
|
|
</div>
|
|
</template>
|
|
|
|
<style>
|
|
@media print {
|
|
.print-page { padding: 0; }
|
|
@page { margin: 2cm; }
|
|
}
|
|
</style>
|