mandant-crm/frontend/pages/clients/[id]/print.vue
Tim Stollberg 570f17e8b8 feat(frontend): commit uncommitted settings, custom fields, and import work
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.
2026-07-18 16:27:19 +07:00

78 lines
2.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>