feat(machine) : single save button + link versioning display

- Replace auto-save-on-blur with single "Enregistrer" button
- Add Cancel button that resets local state
- Expose saveFieldDefinitions via defineExpose on MachineInfoCard
- Remove standalone save button from MachineCustomFieldDefEditor
- Add saveAllMachineCustomFields batch method
- Add submitEdition/cancelEdition/saving/canSubmit to orchestrator
- Show diff summary badges in version list entries
- Show link changes in restore modal description

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matthieu
2026-03-26 16:51:34 +01:00
parent 767c9a7424
commit a7415964a7
8 changed files with 166 additions and 33 deletions

View File

@@ -53,6 +53,15 @@
<span>{{ formatDate(entry.createdAt) }}</span>
<span v-if="entry.actor">&middot; {{ entry.actor.label }}</span>
</div>
<div v-if="entry.diff && Object.keys(entry.diff).length" class="mt-1 flex flex-wrap gap-1">
<span
v-for="(change, field) in entry.diff"
:key="field"
class="badge badge-ghost badge-xs"
>
{{ formatDiffEntry(String(field), change) }}
</span>
</div>
</div>
<button
v-if="canRestore && entry.version !== currentVersion"
@@ -117,6 +126,16 @@ const targetVersion = ref<number | null>(null)
const actionLabel = (action: string) => historyActionLabel(action)
const formatDate = (date: string) => formatHistoryDate(date)
const formatDiffEntry = (field: string, change: { from: unknown; to: unknown }): string => {
const label = props.fieldLabels[field] || field
// Link changes (addedComponent, removedPiece, etc.) have {id, name} as value
const val = change.to ?? change.from
if (val && typeof val === 'object' && 'name' in (val as Record<string, unknown>)) {
return `${label}: ${(val as Record<string, unknown>).name}`
}
return label
}
const handleRestore = async (version: number) => {
targetVersion.value = version
previewData.value = null

View File

@@ -26,6 +26,7 @@
<li>Nom, reference, prix</li>
<li>Site</li>
<li>Fournisseurs</li>
<li>Composants, pieces et produits lies</li>
<li>Champs personnalises</li>
</ul>
</template>

View File

@@ -1,19 +1,8 @@
<template>
<section class="space-y-3">
<div class="flex items-center justify-between">
<h3 class="text-sm font-semibold">
Définitions des champs personnalisés
</h3>
<button
type="button"
class="btn btn-primary btn-sm"
:disabled="saving"
@click="$emit('save')"
>
<span v-if="saving" class="loading loading-spinner loading-xs" />
Enregistrer les champs
</button>
</div>
<h3 class="text-sm font-semibold">
Définitions des champs personnalisés
</h3>
<p v-if="!fields.length" class="text-xs text-gray-500">
Aucun champ personnalisé défini. Cliquez sur « Ajouter » pour en créer un.
@@ -117,7 +106,6 @@ defineProps<{
}>()
defineEmits<{
save: []
'add-field': []
'remove-field': [index: number]
}>()

View File

@@ -14,7 +14,6 @@
type="text"
class="input input-bordered"
@input="$emit('update:machine-name', ($event.target as HTMLInputElement).value)"
@blur="$emit('blur-field')"
/>
<div v-else class="input input-bordered bg-base-200">
{{ machineName }}
@@ -28,7 +27,7 @@
v-if="isEditMode"
:value="machineSiteId"
class="select select-bordered"
@change="$emit('update:machine-site-id', ($event.target as HTMLSelectElement).value); $emit('blur-field')"
@change="$emit('update:machine-site-id', ($event.target as HTMLSelectElement).value)"
>
<option value="">Sélectionner un site</option>
<option
@@ -54,7 +53,6 @@
type="text"
class="input input-bordered"
@input="$emit('update:machine-reference', ($event.target as HTMLInputElement).value)"
@blur="$emit('blur-field')"
/>
<div v-else class="input input-bordered bg-base-200">
{{ machineReference }}
@@ -115,7 +113,6 @@
class="input input-bordered input-sm"
:required="field.required"
@input="$emit('set-custom-field-value', field, ($event.target as HTMLInputElement).value)"
@blur="$emit('update-custom-field', field)"
/>
<input
v-else-if="field.type === 'number'"
@@ -124,7 +121,6 @@
class="input input-bordered input-sm"
:required="field.required"
@input="$emit('set-custom-field-value', field, ($event.target as HTMLInputElement).value)"
@blur="$emit('update-custom-field', field)"
/>
<select
v-else-if="field.type === 'select'"
@@ -132,7 +128,6 @@
class="select select-bordered select-sm"
:required="field.required"
@change="$emit('set-custom-field-value', field, ($event.target as HTMLSelectElement).value)"
@blur="$emit('update-custom-field', field)"
>
<option value="">Sélectionner...</option>
<option
@@ -149,7 +144,6 @@
class="toggle toggle-primary toggle-sm"
:checked="String(field.value).toLowerCase() === 'true'"
@change="$emit('set-custom-field-value', field, ($event.target as HTMLInputElement).checked ? 'true' : 'false')"
@blur="$emit('update-custom-field', field)"
>
<span class="text-sm" :class="String(field.value).toLowerCase() === 'true' ? 'text-success font-medium' : 'text-base-content/60'">{{ String(field.value).toLowerCase() === 'true' ? 'Oui' : 'Non' }}</span>
</label>
@@ -160,7 +154,6 @@
class="input input-bordered input-sm"
:required="field.required"
@input="$emit('set-custom-field-value', field, ($event.target as HTMLInputElement).value)"
@blur="$emit('update-custom-field', field)"
/>
<div v-else class="text-xs text-error">
Type de champ non pris en charge
@@ -184,7 +177,6 @@
:on-drag-enter="fieldDefs.onDragEnter"
:on-drop="fieldDefs.onDrop"
:on-drag-end="fieldDefs.onDragEnd"
@save="fieldDefs.saveDefinitions()"
@add-field="fieldDefs.addField()"
@remove-field="fieldDefs.removeField($event)"
/>
@@ -224,9 +216,7 @@ const emit = defineEmits<{
'update:machine-reference': [value: string]
'update:machine-site-id': [value: string]
'update:constructeur-ids': [ids: unknown]
'blur-field': []
'set-custom-field-value': [field: any, value: unknown]
'update-custom-field': [field: any]
'custom-fields-saved': []
}>()
@@ -239,4 +229,8 @@ const fieldDefs = useMachineCustomFieldDefs({
watch(() => props.machineCustomFieldDefs, (newDefs) => {
fieldDefs.reinit(newDefs)
}, { deep: true })
defineExpose({
saveFieldDefinitions: () => fieldDefs.saveDefinitions(),
})
</script>