feat(versioning) : add entity versioning frontend with restore flow
- 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>
This commit is contained in:
98
app/composables/useEntityVersions.ts
Normal file
98
app/composables/useEntityVersions.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { ref, toValue } from 'vue'
|
||||
import { useApi } from '~/composables/useApi'
|
||||
import type { MaybeRef } from 'vue'
|
||||
|
||||
export interface VersionEntry {
|
||||
version: number
|
||||
action: 'create' | 'update' | 'restore' | string
|
||||
createdAt: string
|
||||
actor: { id: string; label: string } | null
|
||||
diff: Record<string, { from: unknown; to: unknown }> | null
|
||||
}
|
||||
|
||||
export interface RestorePreview {
|
||||
version: number
|
||||
restoreMode: 'full' | 'partial'
|
||||
diff: Record<string, { current: unknown; restored: unknown }>
|
||||
warnings: Array<{
|
||||
field: string
|
||||
message: string
|
||||
missingEntityId: string | null
|
||||
missingEntityName: string | null
|
||||
}>
|
||||
snapshot: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface RestoreResult {
|
||||
success: boolean
|
||||
newVersion: number
|
||||
restoredFromVersion: number
|
||||
restoreMode: 'full' | 'partial'
|
||||
warnings: RestorePreview['warnings']
|
||||
}
|
||||
|
||||
const ENTITY_ENDPOINTS: Record<string, string> = {
|
||||
machine: '/machines',
|
||||
composant: '/composants',
|
||||
piece: '/pieces',
|
||||
product: '/products',
|
||||
}
|
||||
|
||||
interface Deps {
|
||||
entityType: MaybeRef<string>
|
||||
entityId: MaybeRef<string>
|
||||
}
|
||||
|
||||
export function useEntityVersions(deps: Deps) {
|
||||
const { get, post } = useApi()
|
||||
|
||||
const versions = ref<VersionEntry[]>([])
|
||||
const loading = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
const getPath = () => {
|
||||
const type = toValue(deps.entityType)
|
||||
const id = toValue(deps.entityId)
|
||||
const base = ENTITY_ENDPOINTS[type]
|
||||
return `${base}/${id}`
|
||||
}
|
||||
|
||||
const fetchVersions = async () => {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const result = await get(`${getPath()}/versions`)
|
||||
if (!result.success) {
|
||||
error.value = result.error ?? 'Impossible de charger les versions.'
|
||||
versions.value = []
|
||||
return
|
||||
}
|
||||
versions.value = result.data?.items ?? []
|
||||
}
|
||||
catch (err: any) {
|
||||
error.value = err?.message ?? 'Erreur inconnue'
|
||||
versions.value = []
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const fetchPreview = async (version: number): Promise<RestorePreview | null> => {
|
||||
const result = await get<RestorePreview>(`${getPath()}/versions/${version}/preview`)
|
||||
if (!result.success || !result.data) {
|
||||
return null
|
||||
}
|
||||
return result.data
|
||||
}
|
||||
|
||||
const restore = async (version: number): Promise<RestoreResult | null> => {
|
||||
const result = await post<RestoreResult>(`${getPath()}/versions/${version}/restore`, {})
|
||||
if (!result.success || !result.data) {
|
||||
return null
|
||||
}
|
||||
return result.data
|
||||
}
|
||||
|
||||
return { versions, loading, error, fetchVersions, fetchPreview, restore }
|
||||
}
|
||||
Reference in New Issue
Block a user