From a72279f978a6b5da1fe8fcf70fc8cd8ad630ef8d Mon Sep 17 00:00:00 2001 From: Matthieu Date: Tue, 24 Mar 2026 22:45:18 +0100 Subject: [PATCH] refactor(component-edit) : replace slot auto-save with deferred save on submit Slot selections (piece, product, subcomponent, quantity) are no longer saved immediately on change. Instead, edits are stored locally and persisted together with base fields and custom fields when the user clicks "Enregistrer les modifications". Co-Authored-By: Claude Opus 4.6 (1M context) --- app/composables/useComponentEdit.ts | 171 ++++++++++++++++------------ app/pages/component/[id]/edit.vue | 16 +-- 2 files changed, 109 insertions(+), 78 deletions(-) diff --git a/app/composables/useComponentEdit.ts b/app/composables/useComponentEdit.ts index 9906274..48dafc6 100644 --- a/app/composables/useComponentEdit.ts +++ b/app/composables/useComponentEdit.ts @@ -269,95 +269,78 @@ export function useComponentEdit(componentId: string) { } }) - // --- Slot selection entries (for selectors) --- + // --- Slot local edits (saved on submit, not auto-saved) --- + + const slotEdits = reactive<{ + pieces: Record + products: Record + subcomponents: Record + }>({ pieces: {}, products: {}, subcomponents: {} }) const pieceSlotEntries = computed(() => { const structure = component.value?.structure if (!structure?.pieces) return [] - return (structure.pieces as any[]).map((slot: any, i: number) => ({ - slotId: slot.slotId, - typePieceId: slot.typePieceId, - selectedPieceId: slot.selectedPieceId ?? null, - quantity: slot.quantity ?? 1, - position: slot.position ?? i, - label: pieceTypeLabelMap.value[slot.typePieceId] || `Pièce #${i + 1}`, - })) + return (structure.pieces as any[]).map((slot: any, i: number) => { + const edits = slotEdits.pieces[slot.slotId] + return { + slotId: slot.slotId, + typePieceId: slot.typePieceId, + selectedPieceId: edits && 'selectedPieceId' in edits ? edits.selectedPieceId : (slot.selectedPieceId ?? null), + quantity: edits && 'quantity' in edits ? edits.quantity! : (slot.quantity ?? 1), + position: slot.position ?? i, + label: pieceTypeLabelMap.value[slot.typePieceId] || `Pièce #${i + 1}`, + } + }) }) const productSlotEntries = computed(() => { const structure = component.value?.structure if (!structure?.products) return [] - return (structure.products as any[]).map((slot: any, i: number) => ({ - slotId: slot.slotId, - typeProductId: slot.typeProductId, - selectedProductId: slot.selectedProductId ?? null, - familyCode: slot.familyCode, - position: slot.position ?? i, - label: productTypeLabelMap.value[slot.typeProductId] || `Produit #${i + 1}`, - })) + return (structure.products as any[]).map((slot: any, i: number) => { + const edits = slotEdits.products[slot.slotId] + return { + slotId: slot.slotId, + typeProductId: slot.typeProductId, + selectedProductId: edits && 'selectedProductId' in edits ? edits.selectedProductId : (slot.selectedProductId ?? null), + familyCode: slot.familyCode, + position: slot.position ?? i, + label: productTypeLabelMap.value[slot.typeProductId] || `Produit #${i + 1}`, + } + }) }) const subcomponentSlotEntries = computed(() => { const structure = component.value?.structure if (!structure?.subcomponents) return [] - return (structure.subcomponents as any[]).map((slot: any, i: number) => ({ - slotId: slot.slotId, - typeComposantId: slot.typeComposantId, - selectedComponentId: slot.selectedComponentId ?? null, - alias: slot.alias, - familyCode: slot.familyCode, - position: slot.position ?? i, - label: slot.alias || `Sous-composant #${i + 1}`, - })) + return (structure.subcomponents as any[]).map((slot: any, i: number) => { + const edits = slotEdits.subcomponents[slot.slotId] + return { + slotId: slot.slotId, + typeComposantId: slot.typeComposantId, + selectedComponentId: edits && 'selectedComposantId' in edits ? edits.selectedComposantId : (slot.selectedComponentId ?? null), + alias: slot.alias, + familyCode: slot.familyCode, + position: slot.position ?? i, + label: slot.alias || `Sous-composant #${i + 1}`, + } + }) }) - const savePieceSlotSelection = async (slotId: string, selectedPieceId: string | null) => { - const result = await patch(`/composant-piece-slots/${slotId}`, { selectedPieceId }) - if (result.success) { - const structure = component.value?.structure - if (structure?.pieces) { - const slot = (structure.pieces as any[]).find((s: any) => s.slotId === slotId) - if (slot) slot.selectedPieceId = selectedPieceId - } - toast.showSuccess('Pièce mise à jour') - } + const setPieceSlotSelection = (slotId: string, selectedPieceId: string | null) => { + slotEdits.pieces[slotId] = { ...slotEdits.pieces[slotId], selectedPieceId } } - const saveProductSlotSelection = async (slotId: string, selectedProductId: string | null) => { - const result = await patch(`/composant-product-slots/${slotId}`, { selectedProductId }) - if (result.success) { - const structure = component.value?.structure - if (structure?.products) { - const slot = (structure.products as any[]).find((s: any) => s.slotId === slotId) - if (slot) slot.selectedProductId = selectedProductId - } - toast.showSuccess('Produit mis à jour') - } + const setProductSlotSelection = (slotId: string, selectedProductId: string | null) => { + slotEdits.products[slotId] = { ...slotEdits.products[slotId], selectedProductId } } - const saveSubcomponentSlotSelection = async (slotId: string, selectedComposantId: string | null) => { - const result = await patch(`/composant-subcomponent-slots/${slotId}`, { selectedComposantId }) - if (result.success) { - const structure = component.value?.structure - if (structure?.subcomponents) { - const slot = (structure.subcomponents as any[]).find((s: any) => s.slotId === slotId) - if (slot) slot.selectedComponentId = selectedComposantId - } - toast.showSuccess('Sous-composant mis à jour') - } + const setSubcomponentSlotSelection = (slotId: string, selectedComposantId: string | null) => { + slotEdits.subcomponents[slotId] = { ...slotEdits.subcomponents[slotId], selectedComposantId } } - const saveSlotQuantity = async (slotId: string, quantity: number) => { + const setSlotQuantity = (slotId: string, quantity: number) => { if (!slotId || quantity < 1) return - const result = await patch(`/composant-piece-slots/${slotId}`, { quantity: Math.max(1, quantity) }) - if (result.success) { - const structure = component.value?.structure - if (structure?.pieces) { - const slot = (structure.pieces as any[]).find((s: any) => s.slotId === slotId) - if (slot) slot.quantity = quantity - } - toast.showSuccess('Quantité mise à jour') - } + slotEdits.pieces[slotId] = { ...slotEdits.pieces[slotId], quantity: Math.max(1, quantity) } } const submitEdition = async () => { @@ -403,6 +386,54 @@ export function useComponentEdit(componentId: string) { ], { customFieldInputs, upsertCustomFieldValue, updateCustomFieldValue, toast }, ) + + // Save slot edits + const slotPromises: Promise[] = [] + for (const [slotId, edits] of Object.entries(slotEdits.pieces)) { + if (Object.keys(edits).length) { + slotPromises.push(patch(`/composant-piece-slots/${slotId}`, { + ...'selectedPieceId' in edits ? { selectedPieceId: edits.selectedPieceId } : {}, + ...'quantity' in edits ? { quantity: edits.quantity } : {}, + })) + } + } + for (const [slotId, edits] of Object.entries(slotEdits.products)) { + if ('selectedProductId' in edits) { + slotPromises.push(patch(`/composant-product-slots/${slotId}`, { selectedProductId: edits.selectedProductId })) + } + } + for (const [slotId, edits] of Object.entries(slotEdits.subcomponents)) { + if ('selectedComposantId' in edits) { + slotPromises.push(patch(`/composant-subcomponent-slots/${slotId}`, { selectedComposantId: edits.selectedComposantId })) + } + } + await Promise.all(slotPromises) + + // Apply slot edits to local structure so UI reflects saved values + const structure = component.value?.structure + if (structure) { + for (const [slotId, edits] of Object.entries(slotEdits.pieces)) { + const slot = (structure.pieces as any[])?.find((s: any) => s.slotId === slotId) + if (slot) { + if ('selectedPieceId' in edits) slot.selectedPieceId = edits.selectedPieceId + if ('quantity' in edits) slot.quantity = edits.quantity + } + } + for (const [slotId, edits] of Object.entries(slotEdits.products)) { + const slot = (structure.products as any[])?.find((s: any) => s.slotId === slotId) + if (slot && 'selectedProductId' in edits) slot.selectedProductId = edits.selectedProductId + } + for (const [slotId, edits] of Object.entries(slotEdits.subcomponents)) { + const slot = (structure.subcomponents as any[])?.find((s: any) => s.slotId === slotId) + if (slot && 'selectedComposantId' in edits) slot.selectedComponentId = edits.selectedComposantId + } + } + + // Reset local slot edits + slotEdits.pieces = {} + slotEdits.products = {} + slotEdits.subcomponents = {} + toast.showSuccess('Composant mis à jour avec succès.') } } @@ -538,10 +569,10 @@ export function useComponentEdit(componentId: string) { handleFilesAdded, refreshDocuments, submitEdition, - saveSlotQuantity, - savePieceSlotSelection, - saveProductSlotSelection, - saveSubcomponentSlotSelection, + setSlotQuantity, + setPieceSlotSelection, + setProductSlotSelection, + setSubcomponentSlotSelection, resolvePieceLabel, resolveProductLabel, resolveSubcomponentLabel, diff --git a/app/pages/component/[id]/edit.vue b/app/pages/component/[id]/edit.vue index 3014a27..dad8e9c 100644 --- a/app/pages/component/[id]/edit.vue +++ b/app/pages/component/[id]/edit.vue @@ -198,7 +198,7 @@ :model-value="slot.selectedPieceId" :disabled="!canEdit || saving" :type-piece-id="slot.typePieceId" - @update:model-value="(value) => savePieceSlotSelection(slot.slotId, value)" + @update:model-value="(value) => setPieceSlotSelection(slot.slotId, value)" />
@@ -209,7 +209,7 @@ class="input input-bordered input-sm w-full text-center" :disabled="!canEdit || saving" title="Quantité" - @change="(e) => saveSlotQuantity(slot.slotId, Number((e.target as HTMLInputElement).value))" + @change="(e) => setSlotQuantity(slot.slotId, Number((e.target as HTMLInputElement).value))" >
@@ -232,7 +232,7 @@ :model-value="slot.selectedProductId" :disabled="!canEdit || saving" :type-product-id="slot.typeProductId" - @update:model-value="(value) => saveProductSlotSelection(slot.slotId, value)" + @update:model-value="(value) => setProductSlotSelection(slot.slotId, value)" /> @@ -253,7 +253,7 @@ :model-value="slot.selectedComponentId" :disabled="!canEdit || saving" :type-composant-id="slot.typeComposantId" - @update:model-value="(value) => saveSubcomponentSlotSelection(slot.slotId, value)" + @update:model-value="(value) => setSubcomponentSlotSelection(slot.slotId, value)" /> @@ -381,10 +381,10 @@ const { removeDocument, handleFilesAdded, submitEdition, - saveSlotQuantity, - savePieceSlotSelection, - saveProductSlotSelection, - saveSubcomponentSlotSelection, + setSlotQuantity, + setPieceSlotSelection, + setProductSlotSelection, + setSubcomponentSlotSelection, resolvePieceLabel, resolveProductLabel, resolveSubcomponentLabel,