This local working tree had accumulated a substantial amount of finished-but-never-committed frontend work: custom client fields (replacing the old static ClientFields with a dynamic ClientCustomFieldCard + useCustomFields), CSV import (useImport + settings/import page), history type management (useHistoryTypes + settings/history-types page), an admin-only route middleware, several new icon components, a shared useApiFetch composable, and a client detail page restructure ([id].vue -> [id]/index.vue). None of it had ever been pushed, so production was running a stale build that was already linking to routes/components that didn't exist server-side (missing IconSettings, 404s on /settings/import, etc). This commit brings the repo in line with local state.
78 lines
2.8 KiB
Vue
78 lines
2.8 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 formatDate = (iso: string) =>
|
||
new Date(iso).toLocaleString('de-DE', { dateStyle: 'short', timeStyle: 'short' })
|
||
|
||
const formatValue = (field: { type: string; value: string | null }) => {
|
||
if (field.value === null || field.value === '') return '—'
|
||
if (field.type === 'date') {
|
||
const d = new Date(field.value)
|
||
return Number.isNaN(d.getTime()) ? field.value : d.toLocaleDateString('de-DE', { dateStyle: 'medium' })
|
||
}
|
||
return field.value
|
||
}
|
||
|
||
onMounted(async () => {
|
||
// Wait for the freshly navigated DOM to render and paint before opening the
|
||
// print dialog, otherwise the preview can capture a blank page.
|
||
await nextTick()
|
||
requestAnimationFrame(() => requestAnimationFrame(() => 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="field in client.custom_fields ?? []" :key="field.definition_id">
|
||
<tr class="border-b border-gray-200">
|
||
<td class="py-1 text-gray-500">{{ field.label }}</td>
|
||
<td class="py-1">{{ formatValue(field) }}</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">
|
||
{{ entry.type_name ?? '—' }}<template v-if="entry.occurred_at"> – {{ formatEventDateTime(entry.occurred_at) }}</template>
|
||
</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>
|