Add searchbar in catalogue and update custom fuild bug
This commit is contained in:
@@ -26,14 +26,33 @@
|
|||||||
</p>
|
</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3">
|
||||||
|
<label class="w-full sm:w-64">
|
||||||
|
<span class="text-xs font-semibold uppercase tracking-wide text-base-content/70">Recherche</span>
|
||||||
|
<input
|
||||||
|
v-model="searchTerm"
|
||||||
|
type="text"
|
||||||
|
class="input input-bordered input-sm w-full mt-1"
|
||||||
|
placeholder="Nom, référence ou catégorie…"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<p class="text-xs text-base-content/50 sm:text-right">
|
||||||
|
{{ filteredComposants.length }} / {{ composantsTotal }} résultat{{ filteredComposants.length > 1 ? 's' : '' }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-if="loadingComposants" class="flex justify-center py-8">
|
<div v-if="loadingComposants" class="flex justify-center py-8">
|
||||||
<span class="loading loading-spinner" aria-hidden="true" />
|
<span class="loading loading-spinner" aria-hidden="true" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p v-else-if="!composantsList.length" class="text-sm text-base-content/70">
|
<p v-else-if="!composantsTotal" class="text-sm text-base-content/70">
|
||||||
Aucun composant n'a encore été créé.
|
Aucun composant n'a encore été créé.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<p v-else-if="!filteredComposants.length" class="text-sm text-base-content/70">
|
||||||
|
Aucun composant ne correspond à votre recherche.
|
||||||
|
</p>
|
||||||
|
|
||||||
<div v-else class="overflow-x-auto">
|
<div v-else class="overflow-x-auto">
|
||||||
<table class="table table-sm md:table-md">
|
<table class="table table-sm md:table-md">
|
||||||
<thead>
|
<thead>
|
||||||
@@ -45,7 +64,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="component in composantsList" :key="component.id">
|
<tr v-for="component in filteredComposants" :key="component.id">
|
||||||
<td>{{ component.name || 'Composant sans nom' }}</td>
|
<td>{{ component.name || 'Composant sans nom' }}</td>
|
||||||
<td>{{ component.typeComposant?.name || '—' }}</td>
|
<td>{{ component.typeComposant?.name || '—' }}</td>
|
||||||
<td>{{ component.reference || '—' }}</td>
|
<td>{{ component.reference || '—' }}</td>
|
||||||
@@ -78,7 +97,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
import { useComposants } from '~/composables/useComposants'
|
import { useComposants } from '~/composables/useComposants'
|
||||||
import { useToast } from '~/composables/useToast'
|
import { useToast } from '~/composables/useToast'
|
||||||
|
|
||||||
@@ -86,6 +105,25 @@ const { showError } = useToast()
|
|||||||
const { composants, loadComposants, loading: loadingComposantsRef, deleteComposant } = useComposants()
|
const { composants, loadComposants, loading: loadingComposantsRef, deleteComposant } = useComposants()
|
||||||
const loadingComposants = computed(() => loadingComposantsRef.value)
|
const loadingComposants = computed(() => loadingComposantsRef.value)
|
||||||
const composantsList = computed(() => composants.value || [])
|
const composantsList = computed(() => composants.value || [])
|
||||||
|
const composantsTotal = computed(() => composantsList.value.length)
|
||||||
|
|
||||||
|
const searchTerm = ref('')
|
||||||
|
const filteredComposants = computed(() => {
|
||||||
|
const term = searchTerm.value.trim().toLowerCase()
|
||||||
|
if (!term) {
|
||||||
|
return composantsList.value
|
||||||
|
}
|
||||||
|
return composantsList.value.filter((component) => {
|
||||||
|
const name = (component?.name || '').toLowerCase()
|
||||||
|
const reference = (component?.reference || '').toLowerCase()
|
||||||
|
const category = (component?.typeComposant?.name || '').toLowerCase()
|
||||||
|
return (
|
||||||
|
name.includes(term) ||
|
||||||
|
reference.includes(term) ||
|
||||||
|
category.includes(term)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
const handleDeleteComponent = async (component: Record<string, any>) => {
|
const handleDeleteComponent = async (component: Record<string, any>) => {
|
||||||
const hasLinkedElements =
|
const hasLinkedElements =
|
||||||
|
|||||||
@@ -863,7 +863,7 @@ const resolveOptions = (field: any): string[] => {
|
|||||||
return option.trim()
|
return option.trim()
|
||||||
}
|
}
|
||||||
if (typeof option === 'object') {
|
if (typeof option === 'object') {
|
||||||
const record = option as Record<string, unknown>
|
const record = option || {}
|
||||||
const keys = ['value', 'label', 'name']
|
const keys = ['value', 'label', 'name']
|
||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
const candidate = record[key]
|
const candidate = record[key]
|
||||||
|
|||||||
@@ -970,21 +970,21 @@ const resolveOptions = (field: any): string[] => {
|
|||||||
const sources = [field?.options, field?.value?.options, field?.value?.choices]
|
const sources = [field?.options, field?.value?.options, field?.value?.choices]
|
||||||
for (const source of sources) {
|
for (const source of sources) {
|
||||||
if (Array.isArray(source)) {
|
if (Array.isArray(source)) {
|
||||||
const mapped = source
|
const mapped = source
|
||||||
.map((option: unknown) => {
|
.map((option: unknown) => {
|
||||||
if (option === null || option === undefined) {
|
if (option === null || option === undefined) {
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
if (typeof option === 'string') {
|
if (typeof option === 'string') {
|
||||||
return option.trim()
|
return option.trim()
|
||||||
}
|
}
|
||||||
if (typeof option === 'object') {
|
if (typeof option === 'object') {
|
||||||
const record = option as Record<string, unknown>
|
const record = option || {}
|
||||||
const keys = ['value', 'label', 'name']
|
const keys = ['value', 'label', 'name']
|
||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
const candidate = record[key]
|
const candidate = record[key]
|
||||||
if (typeof candidate === 'string' && candidate.trim().length > 0) {
|
if (typeof candidate === 'string' && candidate.trim().length > 0) {
|
||||||
return candidate.trim()
|
return candidate.trim()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -524,7 +524,7 @@ import { useApi } from '~/composables/useApi'
|
|||||||
import { useToast } from '~/composables/useToast'
|
import { useToast } from '~/composables/useToast'
|
||||||
import { useDocuments } from '~/composables/useDocuments'
|
import { useDocuments } from '~/composables/useDocuments'
|
||||||
import { getFileIcon } from '~/utils/fileIcons'
|
import { getFileIcon } from '~/utils/fileIcons'
|
||||||
import { sanitizeDefinitionOverrides } from '~/shared/modelUtils'
|
import { sanitizeDefinitionOverrides, normalizeStructureForEditor } from '~/shared/modelUtils'
|
||||||
import { canPreviewDocument, isImageDocument } from '~/utils/documentPreview'
|
import { canPreviewDocument, isImageDocument } from '~/utils/documentPreview'
|
||||||
import ComponentHierarchy from '~/components/ComponentHierarchy.vue'
|
import ComponentHierarchy from '~/components/ComponentHierarchy.vue'
|
||||||
import DocumentUpload from '~/components/DocumentUpload.vue'
|
import DocumentUpload from '~/components/DocumentUpload.vue'
|
||||||
@@ -1340,7 +1340,24 @@ const flattenComponents = (list = []) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
traverse(list)
|
traverse(list)
|
||||||
return result
|
const filtered = result.filter((field) => {
|
||||||
|
const key =
|
||||||
|
field.customFieldId
|
||||||
|
|| field.id
|
||||||
|
|| (field.name ? `${field.name}::${field.type}` : null)
|
||||||
|
|
||||||
|
if (!key) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allowedKeys.size > 0) {
|
||||||
|
return allowedKeys.has(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
return filtered
|
||||||
}
|
}
|
||||||
|
|
||||||
const flattenedComponents = computed(() => flattenComponents(components.value))
|
const flattenedComponents = computed(() => flattenComponents(components.value))
|
||||||
@@ -1618,18 +1635,18 @@ const formatCustomFieldValue = (field) => {
|
|||||||
return 'Non défini'
|
return 'Non défini'
|
||||||
}
|
}
|
||||||
|
|
||||||
const value = field.value ?? ''
|
const value = field.value ?? field.defaultValue ?? ''
|
||||||
if (!value && value !== 0) {
|
if (value === '' || value === null || value === undefined) {
|
||||||
return 'Non défini'
|
return 'Non défini'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (field.type === 'boolean') {
|
if (field.type === 'boolean') {
|
||||||
const normalized = String(value).toLowerCase()
|
const normalized = String(value).toLowerCase()
|
||||||
if (normalized === 'true') return 'Oui'
|
if (normalized === 'true' || normalized === '1') return 'Oui'
|
||||||
if (normalized === 'false') return 'Non'
|
if (normalized === 'false' || normalized === '0') return 'Non'
|
||||||
}
|
}
|
||||||
|
|
||||||
return value
|
return String(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const shouldDisplayCustomField = (field) => {
|
const shouldDisplayCustomField = (field) => {
|
||||||
@@ -1679,35 +1696,249 @@ const summarizeCustomFields = (fields = []) => {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const extractDefinitionName = (definition = {}) => {
|
||||||
|
if (typeof definition?.name === 'string' && definition.name.trim()) {
|
||||||
|
return definition.name.trim()
|
||||||
|
}
|
||||||
|
if (typeof definition?.key === 'string' && definition.key.trim()) {
|
||||||
|
return definition.key.trim()
|
||||||
|
}
|
||||||
|
if (typeof definition?.label === 'string' && definition.label.trim()) {
|
||||||
|
return definition.label.trim()
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const extractDefinitionType = (definition = {}, fallback = 'text') => {
|
||||||
|
const allowed = ['text', 'number', 'select', 'boolean', 'date']
|
||||||
|
const rawType =
|
||||||
|
typeof definition?.type === 'string'
|
||||||
|
? definition.type
|
||||||
|
: typeof definition?.value?.type === 'string'
|
||||||
|
? definition.value.type
|
||||||
|
: typeof fallback === 'string'
|
||||||
|
? fallback
|
||||||
|
: 'text'
|
||||||
|
const normalized = rawType.toLowerCase()
|
||||||
|
return allowed.includes(normalized) ? normalized : 'text'
|
||||||
|
}
|
||||||
|
|
||||||
|
const extractDefinitionRequired = (definition = {}, fallback = false) => {
|
||||||
|
if (typeof definition?.required === 'boolean') {
|
||||||
|
return definition.required
|
||||||
|
}
|
||||||
|
const nested = definition?.value?.required
|
||||||
|
if (typeof nested === 'boolean') {
|
||||||
|
return nested
|
||||||
|
}
|
||||||
|
if (typeof nested === 'string') {
|
||||||
|
const normalized = nested.toLowerCase()
|
||||||
|
if (normalized === 'true' || normalized === '1') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (normalized === 'false' || normalized === '0') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return !!fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
const extractOptionList = (input) => {
|
||||||
|
if (!Array.isArray(input)) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
const mapped = input
|
||||||
|
.map((option) => {
|
||||||
|
if (option === null || option === undefined) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
if (typeof option === 'string') {
|
||||||
|
return option.trim()
|
||||||
|
}
|
||||||
|
if (typeof option === 'object') {
|
||||||
|
const record = option || {}
|
||||||
|
const keys = ['value', 'label', 'name']
|
||||||
|
for (const key of keys) {
|
||||||
|
const candidate = record[key]
|
||||||
|
if (typeof candidate === 'string' && candidate.trim().length > 0) {
|
||||||
|
return candidate.trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const fallback = String(option).trim()
|
||||||
|
return fallback === '[object Object]' ? '' : fallback
|
||||||
|
})
|
||||||
|
.filter((option) => option.length > 0)
|
||||||
|
return mapped.length ? mapped : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const extractDefinitionOptions = (definition = {}) => {
|
||||||
|
const sources = [definition?.options, definition?.value?.options, definition?.value?.choices]
|
||||||
|
for (const source of sources) {
|
||||||
|
const list = extractOptionList(source)
|
||||||
|
if (list) {
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const extractDefinitionDefaultValue = (definition = {}) => {
|
||||||
|
const candidates = [
|
||||||
|
definition?.defaultValue,
|
||||||
|
definition?.value?.defaultValue,
|
||||||
|
definition?.value?.value,
|
||||||
|
definition?.value,
|
||||||
|
definition?.default,
|
||||||
|
]
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
if (candidate === undefined || candidate === null || candidate === '') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (typeof candidate === 'object') {
|
||||||
|
if (candidate === null) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (candidate && typeof candidate === 'object') {
|
||||||
|
const nestedDefault =
|
||||||
|
candidate.defaultValue !== undefined && candidate.defaultValue !== null
|
||||||
|
? candidate.defaultValue
|
||||||
|
: candidate.value
|
||||||
|
if (nestedDefault !== undefined && nestedDefault !== null && nestedDefault !== '') {
|
||||||
|
return nestedDefault
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return candidate
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const coerceValueForType = (type, rawValue) => {
|
||||||
|
if (rawValue === undefined || rawValue === null || rawValue === '') {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
if (type === 'boolean') {
|
||||||
|
const normalized = String(rawValue).toLowerCase()
|
||||||
|
if (normalized === 'true' || normalized === '1') {
|
||||||
|
return 'true'
|
||||||
|
}
|
||||||
|
if (normalized === 'false' || normalized === '0') {
|
||||||
|
return 'false'
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
return String(rawValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeExistingCustomFieldDefinitions = (fields) => {
|
||||||
|
if (!Array.isArray(fields)) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return fields
|
||||||
|
.map((field) => normalizeCustomFieldDefinitionEntry(field))
|
||||||
|
.filter((definition) => definition !== null)
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeCustomFieldDefinitionEntry = (definition = {}) => {
|
||||||
|
const name = extractDefinitionName(definition)
|
||||||
|
if (!name) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const type = extractDefinitionType(definition)
|
||||||
|
const required = extractDefinitionRequired(definition)
|
||||||
|
const options = extractDefinitionOptions(definition)
|
||||||
|
const defaultValue = extractDefinitionDefaultValue(definition)
|
||||||
|
const id = typeof definition?.id === 'string' ? definition.id : undefined
|
||||||
|
const customFieldId = typeof definition?.customFieldId === 'string' ? definition.customFieldId : id
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
customFieldId,
|
||||||
|
name,
|
||||||
|
type,
|
||||||
|
required,
|
||||||
|
options,
|
||||||
|
defaultValue,
|
||||||
|
readOnly: !!definition?.readOnly,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const getStructureCustomFields = (structure) => {
|
const getStructureCustomFields = (structure) => {
|
||||||
if (!structure || typeof structure !== 'object') {
|
if (!structure || typeof structure !== 'object') {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
const customFields = structure.customFields
|
const normalized = normalizeStructureForEditor(structure)
|
||||||
return Array.isArray(customFields) ? customFields : []
|
return Array.isArray(normalized.customFields) ? normalized.customFields : []
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transform custom field values to custom fields format
|
// Transform custom field values to custom fields format
|
||||||
const mergeCustomFieldValuesWithDefinitions = (valueEntries = [], ...definitionSources) => {
|
const normalizeCustomFieldValueEntry = (entry = {}) => {
|
||||||
const normalizedValues = (Array.isArray(valueEntries) ? valueEntries : []).map((entry) => {
|
if (!entry || typeof entry !== 'object') {
|
||||||
const id = entry?.customField?.id ?? entry?.customFieldId ?? null
|
return null
|
||||||
const name = entry?.customField?.name ?? ''
|
}
|
||||||
const type = entry?.customField?.type ?? 'text'
|
|
||||||
const required = !!entry?.customField?.required
|
|
||||||
const options = Array.isArray(entry?.customField?.options) ? entry.customField.options : []
|
|
||||||
|
|
||||||
return {
|
const normalizedDefinition = normalizeCustomFieldDefinitionEntry(entry)
|
||||||
customFieldValueId: entry?.id ?? null,
|
if (!normalizedDefinition) {
|
||||||
id,
|
return null
|
||||||
customFieldId: id,
|
}
|
||||||
name,
|
|
||||||
type,
|
const value = coerceValueForType(
|
||||||
required,
|
normalizedDefinition.type,
|
||||||
options,
|
entry?.value ?? entry?.defaultValue ?? normalizedDefinition.defaultValue ?? '',
|
||||||
value: entry?.value ?? '',
|
)
|
||||||
readOnly: false,
|
|
||||||
}
|
return {
|
||||||
})
|
id: entry?.customFieldValueId ?? entry?.id ?? null,
|
||||||
|
customFieldId:
|
||||||
|
entry?.customFieldId
|
||||||
|
?? normalizedDefinition.customFieldId
|
||||||
|
?? normalizedDefinition.id
|
||||||
|
?? null,
|
||||||
|
customField: {
|
||||||
|
id: normalizedDefinition.id ?? normalizedDefinition.customFieldId ?? null,
|
||||||
|
name: normalizedDefinition.name,
|
||||||
|
type: normalizedDefinition.type,
|
||||||
|
required: normalizedDefinition.required,
|
||||||
|
options: normalizedDefinition.options,
|
||||||
|
defaultValue: normalizedDefinition.defaultValue ?? '',
|
||||||
|
},
|
||||||
|
value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mergeCustomFieldValuesWithDefinitions = (valueEntries = [], ...definitionSources) => {
|
||||||
|
const normalizedValues = (Array.isArray(valueEntries) ? valueEntries : [])
|
||||||
|
.map((entry) => {
|
||||||
|
if (!entry || typeof entry !== 'object') {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizedDefinition = normalizeCustomFieldDefinitionEntry(entry.customField || entry)
|
||||||
|
if (!normalizedDefinition) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = coerceValueForType(
|
||||||
|
normalizedDefinition.type,
|
||||||
|
entry?.value ?? entry?.defaultValue ?? normalizedDefinition.defaultValue ?? '',
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
customFieldValueId: entry?.id ?? entry?.customFieldValueId ?? null,
|
||||||
|
id: normalizedDefinition.id,
|
||||||
|
customFieldId: normalizedDefinition.customFieldId ?? normalizedDefinition.id ?? null,
|
||||||
|
name: normalizedDefinition.name,
|
||||||
|
type: normalizedDefinition.type,
|
||||||
|
required: normalizedDefinition.required,
|
||||||
|
options: normalizedDefinition.options,
|
||||||
|
optionsText: normalizedDefinition.options?.length ? normalizedDefinition.options.join('\n') : '',
|
||||||
|
defaultValue: normalizedDefinition.defaultValue ?? '',
|
||||||
|
value,
|
||||||
|
readOnly: !!entry?.readOnly,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter((entry) => entry !== null)
|
||||||
|
|
||||||
const result = [...normalizedValues]
|
const result = [...normalizedValues]
|
||||||
const keyFor = (item) => item?.id ?? `${item?.name ?? ''}::${item?.type ?? ''}`
|
const keyFor = (item) => item?.id ?? `${item?.name ?? ''}::${item?.type ?? ''}`
|
||||||
@@ -1725,18 +1956,26 @@ const mergeCustomFieldValuesWithDefinitions = (valueEntries = [], ...definitionS
|
|||||||
|
|
||||||
const definitions = definitionSources
|
const definitions = definitionSources
|
||||||
.flatMap((source) => (Array.isArray(source) ? source : []))
|
.flatMap((source) => (Array.isArray(source) ? source : []))
|
||||||
.filter(Boolean)
|
.map((definition) => normalizeCustomFieldDefinitionEntry(definition))
|
||||||
|
.filter((definition) => definition !== null)
|
||||||
|
|
||||||
|
const allowedKeys = new Set()
|
||||||
definitions.forEach((definition) => {
|
definitions.forEach((definition) => {
|
||||||
const normalizedDefinition = {
|
if (!definition) {
|
||||||
id: definition?.id ?? definition?.customFieldId ?? null,
|
return
|
||||||
name: definition?.name ?? '',
|
|
||||||
type: definition?.type ?? 'text',
|
|
||||||
required: !!definition?.required,
|
|
||||||
options: Array.isArray(definition?.options) ? definition.options : [],
|
|
||||||
customFieldId: definition?.id ?? definition?.customFieldId ?? null,
|
|
||||||
readOnly: !!definition?.readOnly,
|
|
||||||
}
|
}
|
||||||
|
if (definition.id) {
|
||||||
|
allowedKeys.add(definition.id)
|
||||||
|
}
|
||||||
|
if (definition.customFieldId) {
|
||||||
|
allowedKeys.add(definition.customFieldId)
|
||||||
|
}
|
||||||
|
if (definition.name) {
|
||||||
|
allowedKeys.add(`${definition.name}::${definition.type}`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
definitions.forEach((normalizedDefinition) => {
|
||||||
const key = normalizedDefinition.id ?? `${normalizedDefinition.name}::${normalizedDefinition.type}`
|
const key = normalizedDefinition.id ?? `${normalizedDefinition.name}::${normalizedDefinition.type}`
|
||||||
if (!key) {
|
if (!key) {
|
||||||
return
|
return
|
||||||
@@ -1762,12 +2001,21 @@ const mergeCustomFieldValuesWithDefinitions = (valueEntries = [], ...definitionS
|
|||||||
if (existing) {
|
if (existing) {
|
||||||
existing.name = existing.name || normalizedDefinition.name
|
existing.name = existing.name || normalizedDefinition.name
|
||||||
existing.type = existing.type || normalizedDefinition.type
|
existing.type = existing.type || normalizedDefinition.type
|
||||||
existing.required = existing.required ?? normalizedDefinition.required
|
existing.required = existing.required || normalizedDefinition.required
|
||||||
if (!existing.options?.length) {
|
if (!existing.options?.length && normalizedDefinition.options?.length) {
|
||||||
existing.options = normalizedDefinition.options
|
existing.options = normalizedDefinition.options
|
||||||
}
|
}
|
||||||
|
if (!existing.defaultValue && normalizedDefinition.defaultValue) {
|
||||||
|
existing.defaultValue = String(normalizedDefinition.defaultValue)
|
||||||
|
if (!existing.value) {
|
||||||
|
existing.value = coerceValueForType(existing.type, normalizedDefinition.defaultValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
existing.customFieldId = existing.customFieldId || normalizedDefinition.id
|
existing.customFieldId = existing.customFieldId || normalizedDefinition.id
|
||||||
existing.readOnly = existing.readOnly && normalizedDefinition.readOnly
|
existing.readOnly = existing.readOnly && normalizedDefinition.readOnly
|
||||||
|
if (!existing.optionsText && normalizedDefinition.options?.length) {
|
||||||
|
existing.optionsText = normalizedDefinition.options.join('\n')
|
||||||
|
}
|
||||||
if (normalizedDefinition.id) {
|
if (normalizedDefinition.id) {
|
||||||
existingMap.set(normalizedDefinition.id, existing)
|
existingMap.set(normalizedDefinition.id, existing)
|
||||||
}
|
}
|
||||||
@@ -1785,7 +2033,9 @@ const mergeCustomFieldValuesWithDefinitions = (valueEntries = [], ...definitionS
|
|||||||
type: normalizedDefinition.type,
|
type: normalizedDefinition.type,
|
||||||
required: normalizedDefinition.required,
|
required: normalizedDefinition.required,
|
||||||
options: normalizedDefinition.options,
|
options: normalizedDefinition.options,
|
||||||
value: '',
|
optionsText: normalizedDefinition.options?.length ? normalizedDefinition.options.join('\n') : '',
|
||||||
|
defaultValue: normalizedDefinition.defaultValue ?? '',
|
||||||
|
value: coerceValueForType(normalizedDefinition.type, normalizedDefinition.defaultValue ?? ''),
|
||||||
readOnly: false,
|
readOnly: false,
|
||||||
}
|
}
|
||||||
result.push(entry)
|
result.push(entry)
|
||||||
@@ -1858,23 +2108,47 @@ const transformCustomFields = (pieces) => {
|
|||||||
const requirement = piece.typeMachinePieceRequirement || {}
|
const requirement = piece.typeMachinePieceRequirement || {}
|
||||||
const typePiece = requirement.typePiece || piece.typePiece || {}
|
const typePiece = requirement.typePiece || piece.typePiece || {}
|
||||||
|
|
||||||
|
const normalizeStructureDefinitions = (structure) => (
|
||||||
|
structure ? normalizeStructureForEditor(structure) : null
|
||||||
|
)
|
||||||
|
|
||||||
|
const normalizedStructureDefinitions = [
|
||||||
|
normalizeStructureDefinitions(piece.definition?.structure),
|
||||||
|
normalizeStructureDefinitions(piece.typePiece?.structure),
|
||||||
|
normalizeStructureDefinitions(typePiece.structure),
|
||||||
|
normalizeStructureDefinitions(typePiece.pieceSkeleton),
|
||||||
|
normalizeStructureDefinitions(piece.typePiece?.pieceSkeleton),
|
||||||
|
normalizeStructureDefinitions(requirement.structure),
|
||||||
|
normalizeStructureDefinitions(requirement.pieceSkeleton),
|
||||||
|
]
|
||||||
|
|
||||||
|
const valueEntries = [
|
||||||
|
...(Array.isArray(piece.customFieldValues) ? piece.customFieldValues : []),
|
||||||
|
...(Array.isArray(piece.customFields)
|
||||||
|
? piece.customFields
|
||||||
|
.map(normalizeCustomFieldValueEntry)
|
||||||
|
.filter((entry) => entry !== null)
|
||||||
|
: []),
|
||||||
|
...(Array.isArray(typePiece.customFieldValues)
|
||||||
|
? typePiece.customFieldValues
|
||||||
|
.map(normalizeCustomFieldValueEntry)
|
||||||
|
.filter((entry) => entry !== null)
|
||||||
|
: []),
|
||||||
|
]
|
||||||
|
|
||||||
const customFields = dedupeCustomFieldEntries(
|
const customFields = dedupeCustomFieldEntries(
|
||||||
mergeCustomFieldValuesWithDefinitions(
|
mergeCustomFieldValuesWithDefinitions(
|
||||||
piece.customFieldValues,
|
valueEntries,
|
||||||
piece.customFields,
|
normalizeExistingCustomFieldDefinitions(piece.customFields),
|
||||||
piece.definition?.customFields,
|
normalizeExistingCustomFieldDefinitions(piece.definition?.customFields),
|
||||||
piece.typePiece?.customFields,
|
normalizeExistingCustomFieldDefinitions(piece.typePiece?.customFields),
|
||||||
typePiece.customFields,
|
normalizeExistingCustomFieldDefinitions(typePiece.customFields),
|
||||||
requirement.typePiece?.customFields,
|
normalizeExistingCustomFieldDefinitions(requirement.typePiece?.customFields),
|
||||||
requirement.customFields,
|
normalizeExistingCustomFieldDefinitions(requirement.customFields),
|
||||||
requirement.definition?.customFields,
|
normalizeExistingCustomFieldDefinitions(requirement.definition?.customFields),
|
||||||
getStructureCustomFields(piece.definition?.structure),
|
...normalizedStructureDefinitions.map((definition) =>
|
||||||
getStructureCustomFields(piece.typePiece?.structure),
|
getStructureCustomFields(definition),
|
||||||
getStructureCustomFields(typePiece.structure),
|
),
|
||||||
getStructureCustomFields(typePiece.pieceSkeleton),
|
|
||||||
getStructureCustomFields(piece.typePiece?.pieceSkeleton),
|
|
||||||
getStructureCustomFields(requirement.structure),
|
|
||||||
getStructureCustomFields(requirement.pieceSkeleton),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1894,26 +2168,53 @@ const transformCustomFields = (pieces) => {
|
|||||||
|
|
||||||
// Transform custom fields for components (now handles nested structure)
|
// Transform custom fields for components (now handles nested structure)
|
||||||
const transformComponentCustomFields = (componentsData) => {
|
const transformComponentCustomFields = (componentsData) => {
|
||||||
|
const normalizeStructureDefinitions = (structure) => (
|
||||||
|
structure ? normalizeStructureForEditor(structure) : null
|
||||||
|
)
|
||||||
|
|
||||||
return (componentsData || []).map((component) => {
|
return (componentsData || []).map((component) => {
|
||||||
const requirement = component.typeMachineComponentRequirement || {}
|
const requirement = component.typeMachineComponentRequirement || {}
|
||||||
const type = requirement.typeComposant || component.typeComposant || {}
|
const type = requirement.typeComposant || component.typeComposant || {}
|
||||||
|
|
||||||
|
const normalizedStructureDefinitions = [
|
||||||
|
normalizeStructureDefinitions(component.definition?.structure),
|
||||||
|
normalizeStructureDefinitions(component.typeComposant?.structure),
|
||||||
|
normalizeStructureDefinitions(type.structure),
|
||||||
|
normalizeStructureDefinitions(type.componentSkeleton),
|
||||||
|
normalizeStructureDefinitions(requirement.structure),
|
||||||
|
normalizeStructureDefinitions(requirement.componentSkeleton),
|
||||||
|
]
|
||||||
|
|
||||||
|
const actualComponent = component.originalComposant || component
|
||||||
|
|
||||||
|
const valueEntries = [
|
||||||
|
...(Array.isArray(component.customFieldValues) ? component.customFieldValues : []),
|
||||||
|
...(Array.isArray(component.customFields)
|
||||||
|
? component.customFields
|
||||||
|
.map(normalizeCustomFieldValueEntry)
|
||||||
|
.filter((entry) => entry !== null)
|
||||||
|
: []),
|
||||||
|
...(Array.isArray(actualComponent?.customFields)
|
||||||
|
? actualComponent.customFields
|
||||||
|
.map(normalizeCustomFieldValueEntry)
|
||||||
|
.filter((entry) => entry !== null)
|
||||||
|
: []),
|
||||||
|
]
|
||||||
|
|
||||||
const customFields = dedupeCustomFieldEntries(
|
const customFields = dedupeCustomFieldEntries(
|
||||||
mergeCustomFieldValuesWithDefinitions(
|
mergeCustomFieldValuesWithDefinitions(
|
||||||
component.customFieldValues,
|
valueEntries,
|
||||||
component.customFields,
|
normalizeExistingCustomFieldDefinitions(component.customFields),
|
||||||
component.definition?.customFields,
|
normalizeExistingCustomFieldDefinitions(component.definition?.customFields),
|
||||||
component.typeComposant?.customFields,
|
normalizeExistingCustomFieldDefinitions(component.typeComposant?.customFields),
|
||||||
type.customFields,
|
normalizeExistingCustomFieldDefinitions(type.customFields),
|
||||||
requirement.typeComposant?.customFields,
|
normalizeExistingCustomFieldDefinitions(actualComponent?.customFields),
|
||||||
requirement.customFields,
|
normalizeExistingCustomFieldDefinitions(requirement.typeComposant?.customFields),
|
||||||
requirement.definition?.customFields,
|
normalizeExistingCustomFieldDefinitions(requirement.customFields),
|
||||||
getStructureCustomFields(component.definition?.structure),
|
normalizeExistingCustomFieldDefinitions(requirement.definition?.customFields),
|
||||||
getStructureCustomFields(component.typeComposant?.structure),
|
...normalizedStructureDefinitions.map((definition) =>
|
||||||
getStructureCustomFields(type.structure),
|
getStructureCustomFields(definition),
|
||||||
getStructureCustomFields(type.componentSkeleton),
|
),
|
||||||
getStructureCustomFields(requirement.structure),
|
|
||||||
getStructureCustomFields(requirement.componentSkeleton),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1947,14 +2248,25 @@ const transformComponentCustomFields = (componentsData) => {
|
|||||||
const syncMachineCustomFields = () => {
|
const syncMachineCustomFields = () => {
|
||||||
if (!machine.value) {
|
if (!machine.value) {
|
||||||
machineCustomFields.value = []
|
machineCustomFields.value = []
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const merged = dedupeCustomFieldEntries(mergeCustomFieldValuesWithDefinitions(
|
const valueEntries = [
|
||||||
machine.value.customFieldValues,
|
...(Array.isArray(machine.value.customFieldValues) ? machine.value.customFieldValues : []),
|
||||||
machine.value.customFields,
|
...(Array.isArray(machine.value.customFields)
|
||||||
machine.value.typeMachine?.customFields,
|
? machine.value.customFields
|
||||||
)).map((field) => ({
|
.map(normalizeCustomFieldValueEntry)
|
||||||
|
.filter((entry) => entry !== null)
|
||||||
|
: []),
|
||||||
|
]
|
||||||
|
|
||||||
|
const merged = dedupeCustomFieldEntries(
|
||||||
|
mergeCustomFieldValuesWithDefinitions(
|
||||||
|
valueEntries,
|
||||||
|
normalizeExistingCustomFieldDefinitions(machine.value.customFields),
|
||||||
|
normalizeExistingCustomFieldDefinitions(machine.value.typeMachine?.customFields),
|
||||||
|
),
|
||||||
|
).map((field) => ({
|
||||||
...field,
|
...field,
|
||||||
readOnly: false,
|
readOnly: false,
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -25,14 +25,33 @@
|
|||||||
</p>
|
</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3">
|
||||||
|
<label class="w-full sm:w-64">
|
||||||
|
<span class="text-xs font-semibold uppercase tracking-wide text-base-content/70">Recherche</span>
|
||||||
|
<input
|
||||||
|
v-model="searchTerm"
|
||||||
|
type="text"
|
||||||
|
class="input input-bordered input-sm w-full mt-1"
|
||||||
|
placeholder="Nom, référence ou catégorie…"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<p class="text-xs text-base-content/50 sm:text-right">
|
||||||
|
{{ filteredPieces.length }} / {{ piecesTotal }} résultat{{ filteredPieces.length > 1 ? 's' : '' }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-if="loadingPieces" class="flex justify-center py-8">
|
<div v-if="loadingPieces" class="flex justify-center py-8">
|
||||||
<span class="loading loading-spinner" aria-hidden="true" />
|
<span class="loading loading-spinner" aria-hidden="true" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p v-else-if="!piecesList.length" class="text-sm text-base-content/70">
|
<p v-else-if="!piecesTotal" class="text-sm text-base-content/70">
|
||||||
Aucune pièce n'a encore été créée.
|
Aucune pièce n'a encore été créée.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<p v-else-if="!filteredPieces.length" class="text-sm text-base-content/70">
|
||||||
|
Aucune pièce ne correspond à votre recherche.
|
||||||
|
</p>
|
||||||
|
|
||||||
<div v-else class="overflow-x-auto">
|
<div v-else class="overflow-x-auto">
|
||||||
<table class="table table-sm md:table-md">
|
<table class="table table-sm md:table-md">
|
||||||
<thead>
|
<thead>
|
||||||
@@ -44,7 +63,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="piece in piecesList" :key="piece.id">
|
<tr v-for="piece in filteredPieces" :key="piece.id">
|
||||||
<td>{{ piece.name || 'Pièce sans nom' }}</td>
|
<td>{{ piece.name || 'Pièce sans nom' }}</td>
|
||||||
<td>{{ piece.typePiece?.name || '—' }}</td>
|
<td>{{ piece.typePiece?.name || '—' }}</td>
|
||||||
<td>{{ piece.reference || '—' }}</td>
|
<td>{{ piece.reference || '—' }}</td>
|
||||||
@@ -77,7 +96,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
import { usePieces } from '~/composables/usePieces'
|
import { usePieces } from '~/composables/usePieces'
|
||||||
import { useToast } from '~/composables/useToast'
|
import { useToast } from '~/composables/useToast'
|
||||||
|
|
||||||
@@ -85,6 +104,25 @@ const { showError } = useToast()
|
|||||||
const { pieces, loadPieces, loading: loadingPiecesRef, deletePiece } = usePieces()
|
const { pieces, loadPieces, loading: loadingPiecesRef, deletePiece } = usePieces()
|
||||||
const loadingPieces = computed(() => loadingPiecesRef.value)
|
const loadingPieces = computed(() => loadingPiecesRef.value)
|
||||||
const piecesList = computed(() => pieces.value || [])
|
const piecesList = computed(() => pieces.value || [])
|
||||||
|
const piecesTotal = computed(() => piecesList.value.length)
|
||||||
|
|
||||||
|
const searchTerm = ref('')
|
||||||
|
const filteredPieces = computed(() => {
|
||||||
|
const term = searchTerm.value.trim().toLowerCase()
|
||||||
|
if (!term) {
|
||||||
|
return piecesList.value
|
||||||
|
}
|
||||||
|
return piecesList.value.filter((piece) => {
|
||||||
|
const name = (piece?.name || '').toLowerCase()
|
||||||
|
const reference = (piece?.reference || '').toLowerCase()
|
||||||
|
const category = (piece?.typePiece?.name || '').toLowerCase()
|
||||||
|
return (
|
||||||
|
name.includes(term) ||
|
||||||
|
reference.includes(term) ||
|
||||||
|
category.includes(term)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
const handleDeletePiece = async (piece: Record<string, any>) => {
|
const handleDeletePiece = async (piece: Record<string, any>) => {
|
||||||
const hasLinkedElements =
|
const hasLinkedElements =
|
||||||
|
|||||||
Reference in New Issue
Block a user