- useEntityVersions composable (list, preview, restore API calls) - EntityVersionList component with auto-refresh after save - VersionRestoreModal with context-aware messages per entity type - Integrate into machine, composant, piece, product detail pages - Add restore action label to historyDisplayUtils - Show structure slots in composant/piece consultation mode Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
152 lines
4.6 KiB
Vue
152 lines
4.6 KiB
Vue
<template>
|
|
<section class="space-y-3 rounded-lg border border-base-200 bg-base-200/40 p-4">
|
|
<header class="flex items-center justify-between gap-3">
|
|
<div>
|
|
<h2 class="font-semibold text-base-content">Versions</h2>
|
|
<p class="text-xs text-base-content/70">
|
|
Historique des versions avec possibilite de restauration.
|
|
</p>
|
|
</div>
|
|
<span v-if="versions.length" class="badge badge-outline">
|
|
{{ versions.length }} version{{ versions.length > 1 ? 's' : '' }}
|
|
</span>
|
|
</header>
|
|
|
|
<div v-if="loading" class="flex items-center gap-2 text-sm text-base-content/70">
|
|
<span class="loading loading-spinner loading-sm" aria-hidden="true" />
|
|
Chargement des versions...
|
|
</div>
|
|
|
|
<div v-else-if="error" class="alert alert-warning">
|
|
<span>{{ error }}</span>
|
|
</div>
|
|
|
|
<p v-else-if="versions.length === 0" class="text-xs text-base-content/70">
|
|
Aucune version enregistree.
|
|
</p>
|
|
|
|
<ul v-else class="max-h-96 space-y-2 overflow-y-auto pr-1">
|
|
<li
|
|
v-for="entry in versions"
|
|
:key="entry.version"
|
|
class="flex items-center justify-between rounded-md border border-base-200 bg-base-100 px-3 py-2"
|
|
>
|
|
<div class="min-w-0 flex-1">
|
|
<div class="flex items-center gap-2">
|
|
<span class="font-mono text-sm font-semibold">v{{ entry.version }}</span>
|
|
<span
|
|
v-if="entry.version === currentVersion"
|
|
class="badge badge-primary badge-sm"
|
|
>
|
|
actuelle
|
|
</span>
|
|
<span
|
|
v-if="entry.action === 'restore'"
|
|
class="badge badge-warning badge-sm"
|
|
>
|
|
restauration
|
|
</span>
|
|
</div>
|
|
<div class="mt-0.5 flex flex-wrap items-center gap-2 text-xs text-base-content/60">
|
|
<span>{{ actionLabel(entry.action) }}</span>
|
|
<span>·</span>
|
|
<span>{{ formatDate(entry.createdAt) }}</span>
|
|
<span v-if="entry.actor">· {{ entry.actor.label }}</span>
|
|
</div>
|
|
</div>
|
|
<button
|
|
v-if="canRestore && entry.version !== currentVersion"
|
|
class="btn btn-ghost btn-xs"
|
|
:disabled="restoring"
|
|
@click="handleRestore(entry.version)"
|
|
>
|
|
Restaurer
|
|
</button>
|
|
</li>
|
|
</ul>
|
|
|
|
<VersionRestoreModal
|
|
:visible="modalVisible"
|
|
:preview="previewData"
|
|
:restoring="restoring"
|
|
:field-labels="fieldLabels"
|
|
:entity-type="entityType"
|
|
@close="modalVisible = false"
|
|
@confirm="confirmRestore"
|
|
/>
|
|
</section>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, computed, onMounted, watch, toRef } from 'vue'
|
|
import { useEntityVersions, type RestorePreview } from '~/composables/useEntityVersions'
|
|
import { usePermissions } from '~/composables/usePermissions'
|
|
import { formatHistoryDate, historyActionLabel } from '~/shared/utils/historyDisplayUtils'
|
|
import VersionRestoreModal from './VersionRestoreModal.vue'
|
|
|
|
const props = defineProps<{
|
|
entityType: 'machine' | 'composant' | 'piece' | 'product'
|
|
entityId: string
|
|
fieldLabels: Record<string, string>
|
|
/** Increment this value to force a refresh of the versions list */
|
|
refreshKey?: number
|
|
}>()
|
|
|
|
const emit = defineEmits<{
|
|
restored: []
|
|
}>()
|
|
|
|
const { canEdit } = usePermissions()
|
|
const canRestore = computed(() => canEdit.value)
|
|
|
|
const { versions, loading, error, fetchVersions, fetchPreview, restore } = useEntityVersions({
|
|
entityType: props.entityType,
|
|
entityId: props.entityId,
|
|
})
|
|
|
|
const currentVersion = computed(() => {
|
|
if (versions.value.length === 0) return null
|
|
return versions.value[0]?.version ?? null
|
|
})
|
|
|
|
const modalVisible = ref(false)
|
|
const previewData = ref<RestorePreview | null>(null)
|
|
const restoring = ref(false)
|
|
const targetVersion = ref<number | null>(null)
|
|
|
|
const actionLabel = (action: string) => historyActionLabel(action)
|
|
const formatDate = (date: string) => formatHistoryDate(date)
|
|
|
|
const handleRestore = async (version: number) => {
|
|
targetVersion.value = version
|
|
previewData.value = null
|
|
modalVisible.value = true
|
|
previewData.value = await fetchPreview(version)
|
|
}
|
|
|
|
const confirmRestore = async () => {
|
|
if (!targetVersion.value) return
|
|
restoring.value = true
|
|
const result = await restore(targetVersion.value)
|
|
restoring.value = false
|
|
if (result?.success) {
|
|
modalVisible.value = false
|
|
await fetchVersions()
|
|
emit('restored')
|
|
}
|
|
else {
|
|
error.value = 'La restauration a echoue.'
|
|
modalVisible.value = false
|
|
}
|
|
}
|
|
|
|
onMounted(() => {
|
|
fetchVersions()
|
|
})
|
|
|
|
// Auto-refresh when parent signals a data change
|
|
watch(toRef(props, 'refreshKey'), () => {
|
|
fetchVersions()
|
|
})
|
|
</script>
|