Compare commits
2 Commits
| 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 pieceSlotEntries = computed(() => {
|
||||||
const structure = component.value?.structure
|
const structure = component.value?.structure
|
||||||
if (!structure?.pieces) return []
|
if (!structure?.pieces) return []
|
||||||
return (structure.pieces as any[]).map((slot: any, i: number) => ({
|
return (structure.pieces as any[]).map((slot: any, i: number) => {
|
||||||
slotId: slot.slotId,
|
const edits = slotEdits.pieces[slot.slotId]
|
||||||
typePieceId: slot.typePieceId,
|
return {
|
||||||
selectedPieceId: slot.selectedPieceId ?? null,
|
slotId: slot.slotId,
|
||||||
quantity: slot.quantity ?? 1,
|
typePieceId: slot.typePieceId,
|
||||||
position: slot.position ?? i,
|
selectedPieceId: edits && 'selectedPieceId' in edits ? edits.selectedPieceId : (slot.selectedPieceId ?? null),
|
||||||
label: pieceTypeLabelMap.value[slot.typePieceId] || `Pièce #${i + 1}`,
|
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 productSlotEntries = computed(() => {
|
||||||
const structure = component.value?.structure
|
const structure = component.value?.structure
|
||||||
if (!structure?.products) return []
|
if (!structure?.products) return []
|
||||||
return (structure.products as any[]).map((slot: any, i: number) => ({
|
return (structure.products as any[]).map((slot: any, i: number) => {
|
||||||
slotId: slot.slotId,
|
const edits = slotEdits.products[slot.slotId]
|
||||||
typeProductId: slot.typeProductId,
|
return {
|
||||||
selectedProductId: slot.selectedProductId ?? null,
|
slotId: slot.slotId,
|
||||||
familyCode: slot.familyCode,
|
typeProductId: slot.typeProductId,
|
||||||
position: slot.position ?? i,
|
selectedProductId: edits && 'selectedProductId' in edits ? edits.selectedProductId : (slot.selectedProductId ?? null),
|
||||||
label: productTypeLabelMap.value[slot.typeProductId] || `Produit #${i + 1}`,
|
familyCode: slot.familyCode,
|
||||||
}))
|
position: slot.position ?? i,
|
||||||
|
label: productTypeLabelMap.value[slot.typeProductId] || `Produit #${i + 1}`,
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const subcomponentSlotEntries = computed(() => {
|
const subcomponentSlotEntries = computed(() => {
|
||||||
const structure = component.value?.structure
|
const structure = component.value?.structure
|
||||||
if (!structure?.subcomponents) return []
|
if (!structure?.subcomponents) return []
|
||||||
return (structure.subcomponents as any[]).map((slot: any, i: number) => ({
|
return (structure.subcomponents as any[]).map((slot: any, i: number) => {
|
||||||
slotId: slot.slotId,
|
const edits = slotEdits.subcomponents[slot.slotId]
|
||||||
typeComposantId: slot.typeComposantId,
|
return {
|
||||||
selectedComponentId: slot.selectedComponentId ?? null,
|
slotId: slot.slotId,
|
||||||
alias: slot.alias,
|
typeComposantId: slot.typeComposantId,
|
||||||
familyCode: slot.familyCode,
|
selectedComponentId: edits && 'selectedComposantId' in edits ? edits.selectedComposantId : (slot.selectedComponentId ?? null),
|
||||||
position: slot.position ?? i,
|
alias: slot.alias,
|
||||||
label: slot.alias || `Sous-composant #${i + 1}`,
|
familyCode: slot.familyCode,
|
||||||
}))
|
position: slot.position ?? i,
|
||||||
|
label: slot.alias || `Sous-composant #${i + 1}`,
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const savePieceSlotSelection = async (slotId: string, selectedPieceId: string | null) => {
|
const setPieceSlotSelection = (slotId: string, selectedPieceId: string | null) => {
|
||||||
const result = await patch(`/composant-piece-slots/${slotId}`, { selectedPieceId })
|
slotEdits.pieces[slotId] = { ...slotEdits.pieces[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 saveProductSlotSelection = async (slotId: string, selectedProductId: string | null) => {
|
const setProductSlotSelection = (slotId: string, selectedProductId: string | null) => {
|
||||||
const result = await patch(`/composant-product-slots/${slotId}`, { selectedProductId })
|
slotEdits.products[slotId] = { ...slotEdits.products[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 saveSubcomponentSlotSelection = async (slotId: string, selectedComposantId: string | null) => {
|
const setSubcomponentSlotSelection = (slotId: string, selectedComposantId: string | null) => {
|
||||||
const result = await patch(`/composant-subcomponent-slots/${slotId}`, { selectedComposantId })
|
slotEdits.subcomponents[slotId] = { ...slotEdits.subcomponents[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 saveSlotQuantity = async (slotId: string, quantity: number) => {
|
const setSlotQuantity = (slotId: string, quantity: number) => {
|
||||||
if (!slotId || quantity < 1) return
|
if (!slotId || quantity < 1) return
|
||||||
const result = await patch(`/composant-piece-slots/${slotId}`, { quantity: Math.max(1, quantity) })
|
slotEdits.pieces[slotId] = { ...slotEdits.pieces[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')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const submitEdition = async () => {
|
const submitEdition = async () => {
|
||||||
@@ -403,6 +386,54 @@ export function useComponentEdit(componentId: string) {
|
|||||||
],
|
],
|
||||||
{ customFieldInputs, upsertCustomFieldValue, updateCustomFieldValue, toast },
|
{ 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.')
|
toast.showSuccess('Composant mis à jour avec succès.')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -538,10 +569,10 @@ export function useComponentEdit(componentId: string) {
|
|||||||
handleFilesAdded,
|
handleFilesAdded,
|
||||||
refreshDocuments,
|
refreshDocuments,
|
||||||
submitEdition,
|
submitEdition,
|
||||||
saveSlotQuantity,
|
setSlotQuantity,
|
||||||
savePieceSlotSelection,
|
setPieceSlotSelection,
|
||||||
saveProductSlotSelection,
|
setProductSlotSelection,
|
||||||
saveSubcomponentSlotSelection,
|
setSubcomponentSlotSelection,
|
||||||
resolvePieceLabel,
|
resolvePieceLabel,
|
||||||
resolveProductLabel,
|
resolveProductLabel,
|
||||||
resolveSubcomponentLabel,
|
resolveSubcomponentLabel,
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export type EntityHistoryEntry = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ENTITY_ENDPOINTS: Record<string, string> = {
|
const ENTITY_ENDPOINTS: Record<string, string> = {
|
||||||
|
machine: '/machines',
|
||||||
composant: '/composants',
|
composant: '/composants',
|
||||||
piece: '/pieces',
|
piece: '/pieces',
|
||||||
product: '/products',
|
product: '/products',
|
||||||
@@ -35,7 +36,7 @@ const extractItems = (payload: any): EntityHistoryEntry[] => {
|
|||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useEntityHistory(entityType: 'composant' | 'piece' | 'product') {
|
export function useEntityHistory(entityType: 'machine' | 'composant' | 'piece' | 'product') {
|
||||||
const { get } = useApi()
|
const { get } = useApi()
|
||||||
const basePath = ENTITY_ENDPOINTS[entityType]
|
const basePath = ENTITY_ENDPOINTS[entityType]
|
||||||
|
|
||||||
|
|||||||
@@ -198,7 +198,7 @@
|
|||||||
:model-value="slot.selectedPieceId"
|
:model-value="slot.selectedPieceId"
|
||||||
:disabled="!canEdit || saving"
|
:disabled="!canEdit || saving"
|
||||||
:type-piece-id="slot.typePieceId"
|
:type-piece-id="slot.typePieceId"
|
||||||
@update:model-value="(value) => savePieceSlotSelection(slot.slotId, value)"
|
@update:model-value="(value) => setPieceSlotSelection(slot.slotId, value)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-20 shrink-0">
|
<div class="w-20 shrink-0">
|
||||||
@@ -209,7 +209,7 @@
|
|||||||
class="input input-bordered input-sm w-full text-center"
|
class="input input-bordered input-sm w-full text-center"
|
||||||
:disabled="!canEdit || saving"
|
:disabled="!canEdit || saving"
|
||||||
title="Quantité"
|
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>
|
||||||
</div>
|
</div>
|
||||||
@@ -232,7 +232,7 @@
|
|||||||
:model-value="slot.selectedProductId"
|
:model-value="slot.selectedProductId"
|
||||||
:disabled="!canEdit || saving"
|
:disabled="!canEdit || saving"
|
||||||
:type-product-id="slot.typeProductId"
|
:type-product-id="slot.typeProductId"
|
||||||
@update:model-value="(value) => saveProductSlotSelection(slot.slotId, value)"
|
@update:model-value="(value) => setProductSlotSelection(slot.slotId, value)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -253,7 +253,7 @@
|
|||||||
:model-value="slot.selectedComponentId"
|
:model-value="slot.selectedComponentId"
|
||||||
:disabled="!canEdit || saving"
|
:disabled="!canEdit || saving"
|
||||||
:type-composant-id="slot.typeComposantId"
|
:type-composant-id="slot.typeComposantId"
|
||||||
@update:model-value="(value) => saveSubcomponentSlotSelection(slot.slotId, value)"
|
@update:model-value="(value) => setSubcomponentSlotSelection(slot.slotId, value)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -381,10 +381,10 @@ const {
|
|||||||
removeDocument,
|
removeDocument,
|
||||||
handleFilesAdded,
|
handleFilesAdded,
|
||||||
submitEdition,
|
submitEdition,
|
||||||
saveSlotQuantity,
|
setSlotQuantity,
|
||||||
savePieceSlotSelection,
|
setPieceSlotSelection,
|
||||||
saveProductSlotSelection,
|
setProductSlotSelection,
|
||||||
saveSubcomponentSlotSelection,
|
setSubcomponentSlotSelection,
|
||||||
resolvePieceLabel,
|
resolvePieceLabel,
|
||||||
resolveProductLabel,
|
resolveProductLabel,
|
||||||
resolveSubcomponentLabel,
|
resolveSubcomponentLabel,
|
||||||
|
|||||||
@@ -138,6 +138,14 @@
|
|||||||
@confirm="handleAddEntity"
|
@confirm="handleAddEntity"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- Historique -->
|
||||||
|
<EntityHistorySection
|
||||||
|
:entries="history"
|
||||||
|
:loading="historyLoading"
|
||||||
|
:error="historyError"
|
||||||
|
:field-labels="historyFieldLabels"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- Comments -->
|
<!-- Comments -->
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<CommentSection
|
<CommentSection
|
||||||
@@ -181,6 +189,7 @@
|
|||||||
import { computed, ref, onMounted } from 'vue'
|
import { computed, ref, onMounted } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import { useMachineDetailData } from '~/composables/useMachineDetailData'
|
import { useMachineDetailData } from '~/composables/useMachineDetailData'
|
||||||
|
import { useEntityHistory } from '~/composables/useEntityHistory'
|
||||||
import DocumentPreviewModal from '~/components/DocumentPreviewModal.vue'
|
import DocumentPreviewModal from '~/components/DocumentPreviewModal.vue'
|
||||||
import PageHero from '~/components/PageHero.vue'
|
import PageHero from '~/components/PageHero.vue'
|
||||||
import MachinePrintSelectionModal from '~/components/MachinePrintSelectionModal.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 MachineComponentsCard from '~/components/machine/MachineComponentsCard.vue'
|
||||||
import MachinePiecesCard from '~/components/machine/MachinePiecesCard.vue'
|
import MachinePiecesCard from '~/components/machine/MachinePiecesCard.vue'
|
||||||
import AddEntityToMachineModal from '~/components/machine/AddEntityToMachineModal.vue'
|
import AddEntityToMachineModal from '~/components/machine/AddEntityToMachineModal.vue'
|
||||||
|
import EntityHistorySection from '~/components/common/EntityHistorySection.vue'
|
||||||
import IconLucideAlertTriangle from '~icons/lucide/alert-triangle'
|
import IconLucideAlertTriangle from '~icons/lucide/alert-triangle'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
@@ -203,6 +213,21 @@ if (!machineId) {
|
|||||||
|
|
||||||
const d = useMachineDetailData(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 addModalOpen = ref(false)
|
||||||
const addModalKind = ref('component')
|
const addModalKind = ref('component')
|
||||||
|
|
||||||
@@ -228,6 +253,7 @@ const machineViewTitle = computed(() => {
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
d.loadMachineData()
|
d.loadMachineData()
|
||||||
d.loadInitialData()
|
d.loadInitialData()
|
||||||
|
loadHistory(String(machineId)).catch(() => {})
|
||||||
|
|
||||||
if (route.query.edit === 'true' && canEdit.value) {
|
if (route.query.edit === 'true' && canEdit.value) {
|
||||||
d.isEditMode.value = true
|
d.isEditMode.value = true
|
||||||
|
|||||||
Reference in New Issue
Block a user