Add searchbar in catalogue and update custom fuild bug

This commit is contained in:
Matthieu
2025-10-17 10:10:52 +02:00
parent 42c788103a
commit 553600c34b
5 changed files with 485 additions and 97 deletions

View File

@@ -26,14 +26,33 @@
</p>
</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">
<span class="loading loading-spinner" aria-hidden="true" />
</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éé.
</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">
<table class="table table-sm md:table-md">
<thead>
@@ -45,7 +64,7 @@
</tr>
</thead>
<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.typeComposant?.name || '—' }}</td>
<td>{{ component.reference || '—' }}</td>
@@ -78,7 +97,7 @@
</template>
<script setup lang="ts">
import { computed, onMounted } from 'vue'
import { computed, onMounted, ref } from 'vue'
import { useComposants } from '~/composables/useComposants'
import { useToast } from '~/composables/useToast'
@@ -86,6 +105,25 @@ const { showError } = useToast()
const { composants, loadComposants, loading: loadingComposantsRef, deleteComposant } = useComposants()
const loadingComposants = computed(() => loadingComposantsRef.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 hasLinkedElements =

View File

@@ -863,7 +863,7 @@ const resolveOptions = (field: any): string[] => {
return option.trim()
}
if (typeof option === 'object') {
const record = option as Record<string, unknown>
const record = option || {}
const keys = ['value', 'label', 'name']
for (const key of keys) {
const candidate = record[key]

View File

@@ -970,21 +970,21 @@ const resolveOptions = (field: any): string[] => {
const sources = [field?.options, field?.value?.options, field?.value?.choices]
for (const source of sources) {
if (Array.isArray(source)) {
const mapped = source
.map((option: unknown) => {
if (option === null || option === undefined) {
return ''
}
if (typeof option === 'string') {
return option.trim()
}
if (typeof option === 'object') {
const record = option as Record<string, unknown>
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 mapped = source
.map((option: unknown) => {
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()
}
}
}

View File

@@ -524,7 +524,7 @@ import { useApi } from '~/composables/useApi'
import { useToast } from '~/composables/useToast'
import { useDocuments } from '~/composables/useDocuments'
import { getFileIcon } from '~/utils/fileIcons'
import { sanitizeDefinitionOverrides } from '~/shared/modelUtils'
import { sanitizeDefinitionOverrides, normalizeStructureForEditor } from '~/shared/modelUtils'
import { canPreviewDocument, isImageDocument } from '~/utils/documentPreview'
import ComponentHierarchy from '~/components/ComponentHierarchy.vue'
import DocumentUpload from '~/components/DocumentUpload.vue'
@@ -1340,7 +1340,24 @@ const flattenComponents = (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))
@@ -1618,18 +1635,18 @@ const formatCustomFieldValue = (field) => {
return 'Non défini'
}
const value = field.value ?? ''
if (!value && value !== 0) {
const value = field.value ?? field.defaultValue ?? ''
if (value === '' || value === null || value === undefined) {
return 'Non défini'
}
if (field.type === 'boolean') {
const normalized = String(value).toLowerCase()
if (normalized === 'true') return 'Oui'
if (normalized === 'false') return 'Non'
if (normalized === 'true' || normalized === '1') return 'Oui'
if (normalized === 'false' || normalized === '0') return 'Non'
}
return value
return String(value)
}
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) => {
if (!structure || typeof structure !== 'object') {
return []
}
const customFields = structure.customFields
return Array.isArray(customFields) ? customFields : []
const normalized = normalizeStructureForEditor(structure)
return Array.isArray(normalized.customFields) ? normalized.customFields : []
}
// Transform custom field values to custom fields format
const mergeCustomFieldValuesWithDefinitions = (valueEntries = [], ...definitionSources) => {
const normalizedValues = (Array.isArray(valueEntries) ? valueEntries : []).map((entry) => {
const id = entry?.customField?.id ?? entry?.customFieldId ?? 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 : []
const normalizeCustomFieldValueEntry = (entry = {}) => {
if (!entry || typeof entry !== 'object') {
return null
}
return {
customFieldValueId: entry?.id ?? null,
id,
customFieldId: id,
name,
type,
required,
options,
value: entry?.value ?? '',
readOnly: false,
}
})
const normalizedDefinition = normalizeCustomFieldDefinitionEntry(entry)
if (!normalizedDefinition) {
return null
}
const value = coerceValueForType(
normalizedDefinition.type,
entry?.value ?? entry?.defaultValue ?? normalizedDefinition.defaultValue ?? '',
)
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 keyFor = (item) => item?.id ?? `${item?.name ?? ''}::${item?.type ?? ''}`
@@ -1725,18 +1956,26 @@ const mergeCustomFieldValuesWithDefinitions = (valueEntries = [], ...definitionS
const definitions = definitionSources
.flatMap((source) => (Array.isArray(source) ? source : []))
.filter(Boolean)
.map((definition) => normalizeCustomFieldDefinitionEntry(definition))
.filter((definition) => definition !== null)
const allowedKeys = new Set()
definitions.forEach((definition) => {
const normalizedDefinition = {
id: definition?.id ?? definition?.customFieldId ?? null,
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) {
return
}
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}`
if (!key) {
return
@@ -1762,12 +2001,21 @@ const mergeCustomFieldValuesWithDefinitions = (valueEntries = [], ...definitionS
if (existing) {
existing.name = existing.name || normalizedDefinition.name
existing.type = existing.type || normalizedDefinition.type
existing.required = existing.required ?? normalizedDefinition.required
if (!existing.options?.length) {
existing.required = existing.required || normalizedDefinition.required
if (!existing.options?.length && normalizedDefinition.options?.length) {
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.readOnly = existing.readOnly && normalizedDefinition.readOnly
if (!existing.optionsText && normalizedDefinition.options?.length) {
existing.optionsText = normalizedDefinition.options.join('\n')
}
if (normalizedDefinition.id) {
existingMap.set(normalizedDefinition.id, existing)
}
@@ -1785,7 +2033,9 @@ const mergeCustomFieldValuesWithDefinitions = (valueEntries = [], ...definitionS
type: normalizedDefinition.type,
required: normalizedDefinition.required,
options: normalizedDefinition.options,
value: '',
optionsText: normalizedDefinition.options?.length ? normalizedDefinition.options.join('\n') : '',
defaultValue: normalizedDefinition.defaultValue ?? '',
value: coerceValueForType(normalizedDefinition.type, normalizedDefinition.defaultValue ?? ''),
readOnly: false,
}
result.push(entry)
@@ -1858,23 +2108,47 @@ const transformCustomFields = (pieces) => {
const requirement = piece.typeMachinePieceRequirement || {}
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(
mergeCustomFieldValuesWithDefinitions(
piece.customFieldValues,
piece.customFields,
piece.definition?.customFields,
piece.typePiece?.customFields,
typePiece.customFields,
requirement.typePiece?.customFields,
requirement.customFields,
requirement.definition?.customFields,
getStructureCustomFields(piece.definition?.structure),
getStructureCustomFields(piece.typePiece?.structure),
getStructureCustomFields(typePiece.structure),
getStructureCustomFields(typePiece.pieceSkeleton),
getStructureCustomFields(piece.typePiece?.pieceSkeleton),
getStructureCustomFields(requirement.structure),
getStructureCustomFields(requirement.pieceSkeleton),
valueEntries,
normalizeExistingCustomFieldDefinitions(piece.customFields),
normalizeExistingCustomFieldDefinitions(piece.definition?.customFields),
normalizeExistingCustomFieldDefinitions(piece.typePiece?.customFields),
normalizeExistingCustomFieldDefinitions(typePiece.customFields),
normalizeExistingCustomFieldDefinitions(requirement.typePiece?.customFields),
normalizeExistingCustomFieldDefinitions(requirement.customFields),
normalizeExistingCustomFieldDefinitions(requirement.definition?.customFields),
...normalizedStructureDefinitions.map((definition) =>
getStructureCustomFields(definition),
),
),
)
@@ -1894,26 +2168,53 @@ const transformCustomFields = (pieces) => {
// Transform custom fields for components (now handles nested structure)
const transformComponentCustomFields = (componentsData) => {
const normalizeStructureDefinitions = (structure) => (
structure ? normalizeStructureForEditor(structure) : null
)
return (componentsData || []).map((component) => {
const requirement = component.typeMachineComponentRequirement || {}
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(
mergeCustomFieldValuesWithDefinitions(
component.customFieldValues,
component.customFields,
component.definition?.customFields,
component.typeComposant?.customFields,
type.customFields,
requirement.typeComposant?.customFields,
requirement.customFields,
requirement.definition?.customFields,
getStructureCustomFields(component.definition?.structure),
getStructureCustomFields(component.typeComposant?.structure),
getStructureCustomFields(type.structure),
getStructureCustomFields(type.componentSkeleton),
getStructureCustomFields(requirement.structure),
getStructureCustomFields(requirement.componentSkeleton),
valueEntries,
normalizeExistingCustomFieldDefinitions(component.customFields),
normalizeExistingCustomFieldDefinitions(component.definition?.customFields),
normalizeExistingCustomFieldDefinitions(component.typeComposant?.customFields),
normalizeExistingCustomFieldDefinitions(type.customFields),
normalizeExistingCustomFieldDefinitions(actualComponent?.customFields),
normalizeExistingCustomFieldDefinitions(requirement.typeComposant?.customFields),
normalizeExistingCustomFieldDefinitions(requirement.customFields),
normalizeExistingCustomFieldDefinitions(requirement.definition?.customFields),
...normalizedStructureDefinitions.map((definition) =>
getStructureCustomFields(definition),
),
),
)
@@ -1947,14 +2248,25 @@ const transformComponentCustomFields = (componentsData) => {
const syncMachineCustomFields = () => {
if (!machine.value) {
machineCustomFields.value = []
return
return
}
const merged = dedupeCustomFieldEntries(mergeCustomFieldValuesWithDefinitions(
machine.value.customFieldValues,
machine.value.customFields,
machine.value.typeMachine?.customFields,
)).map((field) => ({
const valueEntries = [
...(Array.isArray(machine.value.customFieldValues) ? machine.value.customFieldValues : []),
...(Array.isArray(machine.value.customFields)
? machine.value.customFields
.map(normalizeCustomFieldValueEntry)
.filter((entry) => entry !== null)
: []),
]
const merged = dedupeCustomFieldEntries(
mergeCustomFieldValuesWithDefinitions(
valueEntries,
normalizeExistingCustomFieldDefinitions(machine.value.customFields),
normalizeExistingCustomFieldDefinitions(machine.value.typeMachine?.customFields),
),
).map((field) => ({
...field,
readOnly: false,
}))

View File

@@ -25,14 +25,33 @@
</p>
</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">
<span class="loading loading-spinner" aria-hidden="true" />
</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.
</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">
<table class="table table-sm md:table-md">
<thead>
@@ -44,7 +63,7 @@
</tr>
</thead>
<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.typePiece?.name || '—' }}</td>
<td>{{ piece.reference || '—' }}</td>
@@ -77,7 +96,7 @@
</template>
<script setup lang="ts">
import { computed, onMounted } from 'vue'
import { computed, onMounted, ref } from 'vue'
import { usePieces } from '~/composables/usePieces'
import { useToast } from '~/composables/useToast'
@@ -85,6 +104,25 @@ const { showError } = useToast()
const { pieces, loadPieces, loading: loadingPiecesRef, deletePiece } = usePieces()
const loadingPieces = computed(() => loadingPiecesRef.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 hasLinkedElements =