Show component selections and support multi product requirements
This commit is contained in:
@@ -146,12 +146,26 @@
|
||||
<span>{{ description }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
<ProductSelect
|
||||
v-model="editionForm.productId"
|
||||
:disabled="saving"
|
||||
:type-product-id="primaryProductRequirement?.typeProductId || null"
|
||||
helper-text="Un produit valide est requis pour cette pièce."
|
||||
/>
|
||||
<div class="grid grid-cols-1 gap-3 md:grid-cols-2">
|
||||
<div
|
||||
v-for="entry in productRequirementEntries"
|
||||
:key="entry.key"
|
||||
class="form-control"
|
||||
>
|
||||
<label class="label">
|
||||
<span class="label-text text-xs font-medium">
|
||||
{{ entry.label }}
|
||||
</span>
|
||||
</label>
|
||||
<ProductSelect
|
||||
:model-value="productSelections[entry.index] || null"
|
||||
:disabled="saving"
|
||||
:type-product-id="entry.typeProductId"
|
||||
helper-text="Un produit valide est requis pour cette pièce."
|
||||
@update:model-value="(value) => setProductSelection(entry.index, value)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="selectedType || resolvedStructure" class="space-y-3 rounded-lg border border-base-200 bg-base-200/40 p-4">
|
||||
@@ -448,8 +462,8 @@ const editionForm = reactive({
|
||||
reference: '' as string,
|
||||
constructeurIds: [] as string[],
|
||||
prix: '' as string,
|
||||
productId: null as string | null,
|
||||
})
|
||||
const productSelections = ref<(string | null)[]>([])
|
||||
|
||||
const customFieldInputs = ref<CustomFieldInput[]>([])
|
||||
const documentIcon = (doc: any) =>
|
||||
@@ -592,14 +606,18 @@ const selectedType = computed(() => {
|
||||
return pieceTypeList.value.find((type) => type.id === selectedTypeId.value) ?? null
|
||||
})
|
||||
|
||||
const getStructureProducts = (structure: PieceModelStructure | null) =>
|
||||
Array.isArray(structure?.products) ? structure.products : []
|
||||
|
||||
const getStructureCustomFields = (structure: PieceModelStructure | null) =>
|
||||
Array.isArray(structure?.customFields) ? structure.customFields : []
|
||||
|
||||
const structureProducts = computed(() =>
|
||||
getStructureProducts(resolvedStructure.value),
|
||||
)
|
||||
|
||||
const requiresProductSelection = computed(() => structureProducts.value.length > 0)
|
||||
|
||||
const primaryProductRequirement = computed(() => structureProducts.value[0] ?? null)
|
||||
|
||||
const describeProductRequirement = (requirement: PieceModelProduct, index: number) => {
|
||||
if (!requirement) {
|
||||
return `Produit ${index + 1}`
|
||||
@@ -628,6 +646,50 @@ const productRequirementDescriptions = computed(() =>
|
||||
),
|
||||
)
|
||||
|
||||
const ensureProductSelections = (count: number) => {
|
||||
const next = Array.from({ length: count }, (_, index) => productSelections.value[index] ?? null)
|
||||
productSelections.value = next
|
||||
}
|
||||
|
||||
let pendingProductIds: string[] = []
|
||||
|
||||
const productRequirementEntries = computed(() =>
|
||||
structureProducts.value.map((requirement, index) => ({
|
||||
index,
|
||||
key: `piece-product-requirement-${index}-${requirement?.typeProductId || 'any'}`,
|
||||
label: describeProductRequirement(requirement, index),
|
||||
typeProductId: requirement?.typeProductId ? String(requirement.typeProductId) : null,
|
||||
})),
|
||||
)
|
||||
|
||||
const productSelectionsFilled = computed(() =>
|
||||
!requiresProductSelection.value ||
|
||||
productRequirementEntries.value.every((entry) => {
|
||||
const value = productSelections.value[entry.index]
|
||||
return typeof value === 'string' && value.trim().length > 0
|
||||
}),
|
||||
)
|
||||
|
||||
const setProductSelection = (index: number, value: string | null) => {
|
||||
const normalized = typeof value === 'string' ? value : null
|
||||
const next = [...productSelections.value]
|
||||
next[index] = normalized
|
||||
productSelections.value = next
|
||||
}
|
||||
|
||||
watch(structureProducts, (products) => {
|
||||
ensureProductSelections(products.length)
|
||||
if (!pendingProductIds.length || products.length === 0) {
|
||||
return
|
||||
}
|
||||
const next = Array.from(
|
||||
{ length: products.length },
|
||||
(_, index) => pendingProductIds[index] ?? null,
|
||||
)
|
||||
productSelections.value = next
|
||||
pendingProductIds = []
|
||||
})
|
||||
|
||||
const requiredCustomFieldsFilled = computed(() =>
|
||||
customFieldInputs.value.every((field) => {
|
||||
if (!field.required) {
|
||||
@@ -645,7 +707,7 @@ const canSubmit = computed(() =>
|
||||
piece.value &&
|
||||
editionForm.name &&
|
||||
requiredCustomFieldsFilled.value &&
|
||||
(!requiresProductSelection.value || editionForm.productId) &&
|
||||
productSelectionsFilled.value &&
|
||||
!saving.value,
|
||||
),
|
||||
)
|
||||
@@ -730,11 +792,26 @@ watch(
|
||||
currentPiece.constructeur ? [currentPiece.constructeur] : [],
|
||||
)
|
||||
editionForm.prix = currentPiece.prix !== null && currentPiece.prix !== undefined ? String(currentPiece.prix) : ''
|
||||
editionForm.productId = currentPiece.product?.id || currentPiece.productId || null
|
||||
if (editionForm.constructeurIds.length) {
|
||||
void ensureConstructeurs(editionForm.constructeurIds)
|
||||
}
|
||||
|
||||
const existingProductIds = Array.isArray(currentPiece.productIds) && currentPiece.productIds.length
|
||||
? currentPiece.productIds.map((id: unknown) => String(id))
|
||||
: currentPiece.product?.id || currentPiece.productId
|
||||
? [String(currentPiece.product?.id || currentPiece.productId)]
|
||||
: []
|
||||
pendingProductIds = existingProductIds
|
||||
ensureProductSelections(structureProducts.value.length)
|
||||
if (existingProductIds.length && structureProducts.value.length) {
|
||||
const next = Array.from(
|
||||
{ length: structureProducts.value.length },
|
||||
(_, index) => existingProductIds[index] ?? null,
|
||||
)
|
||||
productSelections.value = next
|
||||
pendingProductIds = []
|
||||
}
|
||||
|
||||
refreshCustomFieldInputs(currentType?.structure ?? null, currentPiece.customFieldValues)
|
||||
|
||||
initialized = true
|
||||
@@ -755,6 +832,7 @@ watch(resolvedStructure, (currentStructure) => {
|
||||
if (!piece.value) {
|
||||
return
|
||||
}
|
||||
ensureProductSelections(structureProducts.value.length)
|
||||
refreshCustomFieldInputs(currentStructure, piece.value.customFieldValues)
|
||||
})
|
||||
|
||||
@@ -763,7 +841,7 @@ const submitEdition = async () => {
|
||||
return
|
||||
}
|
||||
|
||||
if (requiresProductSelection.value && !editionForm.productId) {
|
||||
if (!productSelectionsFilled.value) {
|
||||
toast.showError('Sélectionnez un produit conforme au squelette.')
|
||||
return
|
||||
}
|
||||
@@ -784,11 +862,13 @@ const submitEdition = async () => {
|
||||
const reference = editionForm.reference.trim()
|
||||
payload.reference = reference ? reference : null
|
||||
|
||||
const selectedProductId =
|
||||
typeof editionForm.productId === 'string'
|
||||
? editionForm.productId.trim()
|
||||
: ''
|
||||
payload.productId = selectedProductId || null
|
||||
const normalizedProductIds = productRequirementEntries.value
|
||||
.map((entry) => productSelections.value[entry.index])
|
||||
.filter((value): value is string => typeof value === 'string' && value.trim().length > 0)
|
||||
.map((value) => value.trim())
|
||||
|
||||
payload.productIds = normalizedProductIds
|
||||
payload.productId = normalizedProductIds[0] || null
|
||||
|
||||
if (rawPrice) {
|
||||
const parsed = Number(rawPrice)
|
||||
@@ -981,12 +1061,6 @@ const formatDefaultValue = (type: string, defaultValue: any): string => {
|
||||
return String(defaultValue)
|
||||
}
|
||||
|
||||
const getStructureProducts = (structure: PieceModelStructure | null) =>
|
||||
Array.isArray(structure?.products) ? structure.products : []
|
||||
|
||||
const getStructureCustomFields = (structure: PieceModelStructure | null) =>
|
||||
Array.isArray(structure?.customFields) ? structure.customFields : []
|
||||
|
||||
const buildCustomFieldMetadata = (field: CustomFieldInput) => ({
|
||||
customFieldName: field.name,
|
||||
customFieldType: field.type,
|
||||
|
||||
@@ -118,12 +118,26 @@
|
||||
<span>{{ description }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
<ProductSelect
|
||||
v-model="creationForm.productId"
|
||||
:disabled="submitting || !selectedType"
|
||||
:type-product-id="primaryProductRequirement?.typeProductId || null"
|
||||
helper-text="Un produit est requis pour cette pièce."
|
||||
/>
|
||||
<div class="grid grid-cols-1 gap-3 md:grid-cols-2">
|
||||
<div
|
||||
v-for="entry in productRequirementEntries"
|
||||
:key="entry.key"
|
||||
class="form-control"
|
||||
>
|
||||
<label class="label">
|
||||
<span class="label-text text-xs font-medium">
|
||||
{{ entry.label }}
|
||||
</span>
|
||||
</label>
|
||||
<ProductSelect
|
||||
:model-value="productSelections[entry.index] || null"
|
||||
:disabled="submitting || !selectedType"
|
||||
:type-product-id="entry.typeProductId"
|
||||
helper-text="Un produit est requis pour cette pièce."
|
||||
@update:model-value="(value) => setProductSelection(entry.index, value)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="selectedType" class="space-y-3 rounded-lg border border-base-200 bg-base-200/40 p-4">
|
||||
@@ -317,8 +331,8 @@ const creationForm = reactive({
|
||||
reference: '' as string,
|
||||
constructeurIds: [] as string[],
|
||||
prix: '' as string,
|
||||
productId: null as string | null,
|
||||
})
|
||||
const productSelections = ref<(string | null)[]>([])
|
||||
|
||||
const lastSuggestedName = ref('')
|
||||
const customFieldInputs = ref<CustomFieldInput[]>([])
|
||||
@@ -364,14 +378,18 @@ const selectedType = computed(() => {
|
||||
return pieceTypeList.value.find((type) => type.id === selectedTypeId.value) ?? null
|
||||
})
|
||||
|
||||
const getStructureCustomFields = (structure: PieceModelStructure | null) =>
|
||||
Array.isArray(structure?.customFields) ? structure.customFields : []
|
||||
|
||||
const getStructureProducts = (structure: PieceModelStructure | null) =>
|
||||
Array.isArray(structure?.products) ? structure.products : []
|
||||
|
||||
const structureProducts = computed(() =>
|
||||
getStructureProducts(selectedType.value?.structure ?? null),
|
||||
)
|
||||
|
||||
const requiresProductSelection = computed(() => structureProducts.value.length > 0)
|
||||
|
||||
const primaryProductRequirement = computed(() => structureProducts.value[0] ?? null)
|
||||
|
||||
const describeProductRequirement = (requirement: PieceModelProduct, index: number) => {
|
||||
if (!requirement) {
|
||||
return `Produit ${index + 1}`
|
||||
@@ -400,6 +418,39 @@ const productRequirementDescriptions = computed(() =>
|
||||
),
|
||||
)
|
||||
|
||||
const ensureProductSelections = (count: number) => {
|
||||
const next = Array.from({ length: count }, (_, index) => productSelections.value[index] ?? null)
|
||||
productSelections.value = next
|
||||
}
|
||||
|
||||
const productRequirementEntries = computed(() =>
|
||||
structureProducts.value.map((requirement, index) => ({
|
||||
index,
|
||||
key: `piece-create-product-requirement-${index}-${requirement?.typeProductId || 'any'}`,
|
||||
label: describeProductRequirement(requirement, index),
|
||||
typeProductId: requirement?.typeProductId ? String(requirement.typeProductId) : null,
|
||||
})),
|
||||
)
|
||||
|
||||
const productSelectionsFilled = computed(() =>
|
||||
!requiresProductSelection.value ||
|
||||
productRequirementEntries.value.every((entry) => {
|
||||
const value = productSelections.value[entry.index]
|
||||
return typeof value === 'string' && value.trim().length > 0
|
||||
}),
|
||||
)
|
||||
|
||||
const setProductSelection = (index: number, value: string | null) => {
|
||||
const normalized = typeof value === 'string' ? value : null
|
||||
const next = [...productSelections.value]
|
||||
next[index] = normalized
|
||||
productSelections.value = next
|
||||
}
|
||||
|
||||
watch(structureProducts, (products) => {
|
||||
ensureProductSelections(products.length)
|
||||
})
|
||||
|
||||
watch(selectedType, (type) => {
|
||||
if (!type) {
|
||||
clearCreationForm()
|
||||
@@ -411,7 +462,7 @@ watch(selectedType, (type) => {
|
||||
}
|
||||
lastSuggestedName.value = creationForm.name
|
||||
customFieldInputs.value = normalizeCustomFieldInputs(type.structure)
|
||||
creationForm.productId = null
|
||||
productSelections.value = Array.from({ length: structureProducts.value.length }, () => null)
|
||||
})
|
||||
|
||||
const requiredCustomFieldsFilled = computed(() =>
|
||||
@@ -431,7 +482,7 @@ const canSubmit = computed(() =>
|
||||
selectedType.value &&
|
||||
creationForm.name &&
|
||||
requiredCustomFieldsFilled.value &&
|
||||
(!requiresProductSelection.value || creationForm.productId) &&
|
||||
productSelectionsFilled.value &&
|
||||
!submitting.value,
|
||||
),
|
||||
)
|
||||
@@ -449,18 +500,12 @@ const toFieldString = (value: unknown): string => {
|
||||
return ''
|
||||
}
|
||||
|
||||
const getStructureCustomFields = (structure: PieceModelStructure | null) =>
|
||||
Array.isArray(structure?.customFields) ? structure.customFields : []
|
||||
|
||||
const getStructureProducts = (structure: PieceModelStructure | null) =>
|
||||
Array.isArray(structure?.products) ? structure.products : []
|
||||
|
||||
const clearCreationForm = () => {
|
||||
creationForm.name = ''
|
||||
creationForm.reference = ''
|
||||
creationForm.constructeurIds = []
|
||||
creationForm.prix = ''
|
||||
creationForm.productId = null
|
||||
productSelections.value = []
|
||||
lastSuggestedName.value = ''
|
||||
}
|
||||
|
||||
@@ -470,7 +515,7 @@ const submitCreation = async () => {
|
||||
return
|
||||
}
|
||||
|
||||
if (requiresProductSelection.value && !creationForm.productId) {
|
||||
if (!productSelectionsFilled.value) {
|
||||
toast.showError('Sélectionnez un produit conforme au squelette.')
|
||||
return
|
||||
}
|
||||
@@ -487,12 +532,13 @@ const submitCreation = async () => {
|
||||
|
||||
payload.constructeurIds = uniqueConstructeurIds(creationForm.constructeurIds)
|
||||
|
||||
const selectedProductId =
|
||||
typeof creationForm.productId === 'string'
|
||||
? creationForm.productId.trim()
|
||||
: ''
|
||||
if (selectedProductId) {
|
||||
payload.productId = selectedProductId
|
||||
const normalizedProductIds = productRequirementEntries.value
|
||||
.map((entry) => productSelections.value[entry.index])
|
||||
.filter((value): value is string => typeof value === 'string' && value.trim().length > 0)
|
||||
.map((value) => value.trim())
|
||||
if (normalizedProductIds.length) {
|
||||
payload.productIds = normalizedProductIds
|
||||
payload.productId = normalizedProductIds[0]
|
||||
}
|
||||
|
||||
const rawPrice = typeof creationForm.prix === 'string'
|
||||
|
||||
Reference in New Issue
Block a user