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:
@@ -316,11 +316,19 @@
|
||||
:field-labels="historyFieldLabels"
|
||||
/>
|
||||
|
||||
<EntityVersionList
|
||||
entity-type="composant"
|
||||
:entity-id="String(route.params.id)"
|
||||
:field-labels="historyFieldLabels"
|
||||
:refresh-key="versionRefreshKey"
|
||||
@restored="fetchComponent()"
|
||||
/>
|
||||
|
||||
<div class="flex flex-col gap-3 md:flex-row md:justify-end">
|
||||
<NuxtLink to="/component-catalog" class="btn btn-ghost" :class="{ 'btn-disabled': saving }">
|
||||
Annuler
|
||||
</NuxtLink>
|
||||
<button type="button" class="btn btn-primary" :disabled="!canSubmit" @click="submitEdition">
|
||||
<button type="button" class="btn btn-primary" :disabled="!canSubmit" @click="async () => { await submitEdition(); versionRefreshKey++ }">
|
||||
<span v-if="saving" class="loading loading-spinner loading-sm mr-2" />
|
||||
Enregistrer les modifications
|
||||
</button>
|
||||
@@ -349,6 +357,7 @@ import { useDocuments } from '~/composables/useDocuments'
|
||||
|
||||
const route = useRoute()
|
||||
const { updateDocument } = useDocuments()
|
||||
const versionRefreshKey = ref(0)
|
||||
|
||||
const {
|
||||
component,
|
||||
@@ -389,6 +398,7 @@ const {
|
||||
resolveProductLabel,
|
||||
resolveSubcomponentLabel,
|
||||
formatStructurePreview,
|
||||
fetchComponent,
|
||||
} = useComponentEdit(String(route.params.id))
|
||||
|
||||
const editingDocument = ref<any | null>(null)
|
||||
|
||||
@@ -207,15 +207,15 @@
|
||||
:resolve-subcomponent-label="resolveSubcomponentLabel"
|
||||
/>
|
||||
|
||||
<!-- Skeleton slot selections (edit mode only) -->
|
||||
<!-- Skeleton slot selections -->
|
||||
<div
|
||||
v-if="isEditMode && (pieceSlotEntries.length || productSlotEntries.length || subcomponentSlotEntries.length)"
|
||||
v-if="pieceSlotEntries.length || productSlotEntries.length || subcomponentSlotEntries.length"
|
||||
class="space-y-3 rounded-lg border border-base-200 bg-base-200/30 p-4"
|
||||
>
|
||||
<header class="space-y-1">
|
||||
<h2 class="font-semibold text-base-content">Sélections du squelette</h2>
|
||||
<h2 class="font-semibold text-base-content">{{ isEditMode ? 'Sélections du squelette' : 'Structure du composant' }}</h2>
|
||||
<p class="text-xs text-base-content/70">
|
||||
Choisissez les pièces, produits et sous-composants pour chaque emplacement requis par la catégorie.
|
||||
{{ isEditMode ? 'Choisissez les pièces, produits et sous-composants pour chaque emplacement requis par la catégorie.' : 'Pièces, produits et sous-composants associés à ce composant.' }}
|
||||
</p>
|
||||
</header>
|
||||
|
||||
@@ -230,26 +230,32 @@
|
||||
<label class="label">
|
||||
<span class="label-text text-xs font-medium">{{ slot.label }}</span>
|
||||
</label>
|
||||
<div class="flex items-start gap-2">
|
||||
<div class="flex-1">
|
||||
<PieceSelect
|
||||
:model-value="slot.selectedPieceId"
|
||||
:disabled="!canEdit || saving"
|
||||
:type-piece-id="slot.typePieceId"
|
||||
@update:model-value="(value) => setPieceSlotSelection(slot.slotId, value)"
|
||||
/>
|
||||
</div>
|
||||
<div class="w-20 shrink-0">
|
||||
<input
|
||||
type="number"
|
||||
:value="slot.quantity"
|
||||
min="1"
|
||||
class="input input-bordered input-sm w-full text-center"
|
||||
:disabled="!canEdit || saving"
|
||||
title="Quantité"
|
||||
@change="(e) => setSlotQuantity(slot.slotId, Number((e.target as HTMLInputElement).value))"
|
||||
>
|
||||
<template v-if="isEditMode">
|
||||
<div class="flex items-start gap-2">
|
||||
<div class="flex-1">
|
||||
<PieceSelect
|
||||
:model-value="slot.selectedPieceId"
|
||||
:disabled="!canEdit || saving"
|
||||
:type-piece-id="slot.typePieceId"
|
||||
@update:model-value="(value) => setPieceSlotSelection(slot.slotId, value)"
|
||||
/>
|
||||
</div>
|
||||
<div class="w-20 shrink-0">
|
||||
<input
|
||||
type="number"
|
||||
:value="slot.quantity"
|
||||
min="1"
|
||||
class="input input-bordered input-sm w-full text-center"
|
||||
:disabled="!canEdit || saving"
|
||||
title="Quantité"
|
||||
@change="(e) => setSlotQuantity(slot.slotId, Number((e.target as HTMLInputElement).value))"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div v-else class="input input-bordered input-sm md:input-md bg-base-200 flex items-center gap-2">
|
||||
{{ resolvePieceLabel(slot.selectedPieceId) || '— Non sélectionné' }}
|
||||
<span v-if="slot.quantity > 1" class="badge badge-sm">x{{ slot.quantity }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -266,12 +272,17 @@
|
||||
<label class="label">
|
||||
<span class="label-text text-xs font-medium">{{ slot.label }}</span>
|
||||
</label>
|
||||
<ProductSelect
|
||||
:model-value="slot.selectedProductId"
|
||||
:disabled="!canEdit || saving"
|
||||
:type-product-id="slot.typeProductId"
|
||||
@update:model-value="(value) => setProductSlotSelection(slot.slotId, value)"
|
||||
/>
|
||||
<template v-if="isEditMode">
|
||||
<ProductSelect
|
||||
:model-value="slot.selectedProductId"
|
||||
:disabled="!canEdit || saving"
|
||||
:type-product-id="slot.typeProductId"
|
||||
@update:model-value="(value) => setProductSlotSelection(slot.slotId, value)"
|
||||
/>
|
||||
</template>
|
||||
<div v-else class="input input-bordered input-sm md:input-md bg-base-200 flex items-center">
|
||||
{{ resolveProductLabel(slot.selectedProductId) || '— Non sélectionné' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -287,12 +298,17 @@
|
||||
<label class="label">
|
||||
<span class="label-text text-xs font-medium">{{ slot.label }}</span>
|
||||
</label>
|
||||
<ComposantSelect
|
||||
:model-value="slot.selectedComponentId"
|
||||
:disabled="!canEdit || saving"
|
||||
:type-composant-id="slot.typeComposantId"
|
||||
@update:model-value="(value) => setSubcomponentSlotSelection(slot.slotId, value)"
|
||||
/>
|
||||
<template v-if="isEditMode">
|
||||
<ComposantSelect
|
||||
:model-value="slot.selectedComponentId"
|
||||
:disabled="!canEdit || saving"
|
||||
:type-composant-id="slot.typeComposantId"
|
||||
@update:model-value="(value) => setSubcomponentSlotSelection(slot.slotId, value)"
|
||||
/>
|
||||
</template>
|
||||
<div v-else class="input input-bordered input-sm md:input-md bg-base-200 flex items-center">
|
||||
{{ resolveSubcomponentLabel(slot.selectedComponentId) || '— Non sélectionné' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user