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.
90 lines
3.3 KiB
Vue
90 lines
3.3 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, payload: { body: string; occurred_at: string | null }]
|
||
}>()
|
||
|
||
const { user } = useAuth()
|
||
|
||
// Auto-generated change entries are read-only audit records; only the author
|
||
// may edit their own manual notes.
|
||
const canEdit = computed(() => !props.entry.is_field_change && props.entry.author_id === user.value?.id)
|
||
// Only admins may delete Verlauf entries (manual notes and change entries alike).
|
||
const canDelete = computed(() => user.value?.role === 'admin')
|
||
|
||
const editing = ref(false)
|
||
const editBody = ref(props.entry.body)
|
||
// datetime-local wants "YYYY-MM-DDTHH:MM" (no seconds); occurred_at carries seconds.
|
||
const editOccurredAt = ref(props.entry.occurred_at?.slice(0, 16) ?? '')
|
||
|
||
const saveEdit = () => {
|
||
const occurred_at = props.entry.requires_datetime && editOccurredAt.value
|
||
? `${editOccurredAt.value}:00`
|
||
: null
|
||
emit('update', props.entry.id, { body: editBody.value, occurred_at })
|
||
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">
|
||
{{ entry.type_name ?? '—' }}<template v-if="entry.occurred_at"> – {{ formatEventDateTime(entry.occurred_at) }}</template>
|
||
</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-1 shrink-0">
|
||
<button
|
||
v-if="canEdit"
|
||
@click="editing = !editing"
|
||
:title="editing ? 'Abbrechen' : 'Bearbeiten'"
|
||
:aria-label="editing ? 'Abbrechen' : 'Bearbeiten'"
|
||
class="p-1 text-gray-400 hover:text-blue-600"
|
||
>
|
||
<IconClose v-if="editing" />
|
||
<IconEdit v-else />
|
||
</button>
|
||
<button
|
||
v-if="canDelete"
|
||
@click="emit('delete', entry.id)"
|
||
title="Löschen"
|
||
aria-label="Löschen"
|
||
class="p-1 text-gray-400 hover:text-red-600"
|
||
>
|
||
<IconTrash />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div v-if="!editing" class="text-sm text-gray-800 whitespace-pre-wrap">{{ entry.body }}</div>
|
||
<div v-else class="space-y-2">
|
||
<div v-if="entry.requires_datetime">
|
||
<label class="block text-xs font-medium text-gray-600 mb-1">Datum/Uhrzeit</label>
|
||
<input
|
||
v-model="editOccurredAt"
|
||
type="datetime-local"
|
||
class="border border-gray-300 px-2 py-1.5 text-sm"
|
||
/>
|
||
</div>
|
||
<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="bg-blue-600 text-white px-3 py-1 text-sm hover:bg-blue-700">
|
||
Speichern
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</template>
|