Compare commits
2 Commits
9cc8b28122
...
9e1504ddb7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e1504ddb7 | ||
|
|
a72279f978 |
@@ -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<string, { selectedPieceId?: string | null, quantity?: number }>
|
||||
products: Record<string, { selectedProductId?: string | null }>
|
||||
subcomponents: Record<string, { selectedComposantId?: string | null }>
|
||||
}>({ 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<any>[] = []
|
||||
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,
|
||||
|
||||
@@ -23,6 +23,7 @@ export type EntityHistoryEntry = {
|
||||
}
|
||||
|
||||
const ENTITY_ENDPOINTS: Record<string, string> = {
|
||||
machine: '/machines',
|
||||
composant: '/composants',
|
||||
piece: '/pieces',
|
||||
product: '/products',
|
||||
@@ -35,7 +36,7 @@ const extractItems = (payload: any): EntityHistoryEntry[] => {
|
||||
return []
|
||||
}
|
||||
|
||||
export function useEntityHistory(entityType: 'composant' | 'piece' | 'product') {
|
||||
export function useEntityHistory(entityType: 'machine' | 'composant' | 'piece' | 'product') {
|
||||
const { get } = useApi()
|
||||
const basePath = ENTITY_ENDPOINTS[entityType]
|
||||
|
||||
|
||||
@@ -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)"
|
||||
/>
|
||||
</div>
|
||||
<div class="w-20 shrink-0">
|
||||
@@ -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))"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
@@ -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)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -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)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -381,10 +381,10 @@ const {
|
||||
removeDocument,
|
||||
handleFilesAdded,
|
||||
submitEdition,
|
||||
saveSlotQuantity,
|
||||
savePieceSlotSelection,
|
||||
saveProductSlotSelection,
|
||||
saveSubcomponentSlotSelection,
|
||||
setSlotQuantity,
|
||||
setPieceSlotSelection,
|
||||
setProductSlotSelection,
|
||||
setSubcomponentSlotSelection,
|
||||
resolvePieceLabel,
|
||||
resolveProductLabel,
|
||||
resolveSubcomponentLabel,
|
||||
|
||||
@@ -138,6 +138,14 @@
|
||||
@confirm="handleAddEntity"
|
||||
/>
|
||||
|
||||
<!-- Historique -->
|
||||
<EntityHistorySection
|
||||
:entries="history"
|
||||
:loading="historyLoading"
|
||||
:error="historyError"
|
||||
:field-labels="historyFieldLabels"
|
||||
/>
|
||||
|
||||
<!-- Comments -->
|
||||
<div class="mt-4">
|
||||
<CommentSection
|
||||
@@ -181,6 +189,7 @@
|
||||
import { computed, ref, onMounted } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useMachineDetailData } from '~/composables/useMachineDetailData'
|
||||
import { useEntityHistory } from '~/composables/useEntityHistory'
|
||||
import DocumentPreviewModal from '~/components/DocumentPreviewModal.vue'
|
||||
import PageHero from '~/components/PageHero.vue'
|
||||
import MachinePrintSelectionModal from '~/components/MachinePrintSelectionModal.vue'
|
||||
@@ -191,6 +200,7 @@ import MachineProductsCard from '~/components/machine/MachineProductsCard.vue'
|
||||
import MachineComponentsCard from '~/components/machine/MachineComponentsCard.vue'
|
||||
import MachinePiecesCard from '~/components/machine/MachinePiecesCard.vue'
|
||||
import AddEntityToMachineModal from '~/components/machine/AddEntityToMachineModal.vue'
|
||||
import EntityHistorySection from '~/components/common/EntityHistorySection.vue'
|
||||
import IconLucideAlertTriangle from '~icons/lucide/alert-triangle'
|
||||
|
||||
const route = useRoute()
|
||||
@@ -203,6 +213,21 @@ if (!machineId) {
|
||||
|
||||
const d = useMachineDetailData(machineId)
|
||||
|
||||
const {
|
||||
history,
|
||||
loading: historyLoading,
|
||||
error: historyError,
|
||||
loadHistory,
|
||||
} = useEntityHistory('machine')
|
||||
|
||||
const historyFieldLabels = {
|
||||
name: 'Nom',
|
||||
reference: 'Référence',
|
||||
prix: 'Prix',
|
||||
site: 'Site',
|
||||
constructeurIds: 'Fournisseurs',
|
||||
}
|
||||
|
||||
const addModalOpen = ref(false)
|
||||
const addModalKind = ref('component')
|
||||
|
||||
@@ -228,6 +253,7 @@ const machineViewTitle = computed(() => {
|
||||
onMounted(() => {
|
||||
d.loadMachineData()
|
||||
d.loadInitialData()
|
||||
loadHistory(String(machineId)).catch(() => {})
|
||||
|
||||
if (route.query.edit === 'true' && canEdit.value) {
|
||||
d.isEditMode.value = true
|
||||
|
||||
Reference in New Issue
Block a user