Add new dropdown search

This commit is contained in:
Matthieu
2025-10-16 08:51:18 +02:00
parent e297d1bb39
commit 62b5c9b297
10 changed files with 197 additions and 80 deletions

View File

@@ -14,21 +14,17 @@
<label class="label">
<span class="label-text text-xs">Sélectionner un composant</span>
</label>
<select
v-model="assignment.selectedComponentId"
class="select select-bordered select-sm"
>
<option value="">
{{ componentOptions.length ? 'Choisir un composant compatible' : 'Aucun composant disponible' }}
</option>
<option
v-for="component in componentOptions"
:key="component.id"
:value="component.id"
>
{{ formatComponentOption(component) }}
</option>
</select>
<SearchSelect
:model-value="assignment.selectedComponentId || ''"
:options="componentOptions"
:loading="componentsLoading"
size="sm"
placeholder="Rechercher un composant..."
:empty-text="componentOptions.length ? 'Aucun résultat' : 'Aucun composant disponible'"
:option-label="componentOptionLabel"
:option-description="componentOptionDescription"
@update:modelValue="(value) => { assignment.selectedComponentId = normalizeSelectionValue(value); }"
/>
</div>
</section>
@@ -56,21 +52,17 @@
</p>
</div>
<select
v-model="pieceAssignment.selectedPieceId"
class="select select-bordered select-xs"
>
<option value="">
{{ getPieceOptions(pieceAssignment.definition).length ? 'Choisir une pièce' : 'Sélection impossible' }}
</option>
<option
v-for="piece in getPieceOptions(pieceAssignment.definition)"
:key="piece.id"
:value="piece.id"
>
{{ formatPieceOption(piece) }}
</option>
</select>
<SearchSelect
:model-value="pieceAssignment.selectedPieceId || ''"
:options="getPieceOptions(pieceAssignment.definition)"
:loading="piecesLoading"
size="xs"
placeholder="Rechercher une pièce..."
:empty-text="getPieceOptions(pieceAssignment.definition).length ? 'Aucun résultat' : 'Aucune pièce disponible'"
:option-label="pieceOptionLabel"
:option-description="pieceOptionDescription"
@update:modelValue="(value) => { pieceAssignment.selectedPieceId = normalizeSelectionValue(value); }"
/>
</div>
</section>
@@ -90,6 +82,8 @@
:assignment="subAssignment"
:pieces="pieces"
:components="components"
:components-loading="componentsLoading"
:pieces-loading="piecesLoading"
:depth="depth + 1"
/>
</section>
@@ -98,6 +92,7 @@
<script setup lang="ts">
import { computed, watch } from 'vue';
import SearchSelect from '~/components/common/SearchSelect.vue';
import type {
ComponentModelPiece,
ComponentModelStructureNode,
@@ -147,11 +142,15 @@ const props = withDefaults(
pieces: PieceOption[] | null;
components: ComponentOption[] | null;
depth?: number;
componentsLoading?: boolean;
piecesLoading?: boolean;
}>(),
{
depth: 0,
pieces: () => [],
components: () => [],
componentsLoading: false,
piecesLoading: false,
},
);
@@ -188,6 +187,29 @@ const componentOptions = computed(() => {
});
});
const componentOptionLabel = (component?: ComponentOption | null) => {
if (!component) {
return 'Composant sans nom';
}
return component.name || 'Composant sans nom';
};
const componentOptionDescription = (component?: ComponentOption | null) => {
if (!component) {
return '';
}
const parts: string[] = [];
const typeLabel =
component.typeComposant?.name || component.typeComposant?.code || null;
if (typeLabel) {
parts.push(typeLabel);
}
if (component.reference) {
parts.push(`Ref. ${component.reference}`);
}
return parts.join(' • ');
};
watch(
componentOptions,
(options) => {
@@ -204,18 +226,6 @@ watch(
{ immediate: true },
);
const formatComponentOption = (component: ComponentOption) => {
const name = component.name || 'Composant sans nom';
const reference = component.reference ? ` • Ref. ${component.reference}` : '';
const typeLabel =
component.typeComposant?.name || component.typeComposant?.code || '';
const typed =
typeLabel && component.typeComposant?.name !== name
? ` (${typeLabel})`
: '';
return `${name}${typed}${reference}`;
};
const describePieceRequirement = (definition: ComponentModelPiece) => {
const parts: string[] = [];
if (definition.role) {
@@ -288,10 +298,40 @@ const getPieceOptions = (definition: ComponentModelPiece) => {
});
};
const formatPieceOption = (piece: PieceOption) => {
const name = piece.name || 'Pièce';
const reference = piece.reference ? ` • Ref. ${piece.reference}` : '';
return `${name}${reference}`;
const pieceOptionLabel = (piece?: PieceOption | null) => {
if (!piece) {
return 'Pièce';
}
return piece.name || 'Pièce';
};
const pieceOptionDescription = (piece?: PieceOption | null) => {
if (!piece) {
return '';
}
const parts: string[] = [];
const typeLabel =
piece.typePiece?.name || piece.typePiece?.code || null;
if (typeLabel) {
parts.push(typeLabel);
}
if (piece.reference) {
parts.push(`Ref. ${piece.reference}`);
}
return parts.join(' • ');
};
const normalizeSelectionValue = (value: unknown) => {
if (value === null || value === undefined || value === '') {
return '';
}
if (typeof value === 'string') {
return value;
}
if (typeof value === 'number') {
return String(value);
}
return '';
};
watch(