All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 52s
Correcciones aplicadas: - Usar ignore-filter para control manual del filtrado - Implementar label-key y value-key correctamente - Usar slot #item-label en lugar de #item para personalización - Usar slot #empty para mensajes cuando no hay resultados - Mapear items a formato InputMenuItem con label y value - Usar clases de Nuxt UI (text-muted) en lugar de variables CSS - Simplificar lógica eliminando control manual del estado open Funcionalidad: - Filtrado solo se activa con mínimo 4 caracteres - Búsqueda por nombre y cédula - Selección múltiple con tags visuales - Formato de cédula mantenido
142 lines
3.4 KiB
Vue
142 lines
3.4 KiB
Vue
<template>
|
|
<div class="space-y-3">
|
|
<!-- InputMenu for cliente search and selection -->
|
|
<UInputMenu
|
|
v-model="selectedClientes"
|
|
v-model:search-term="searchQuery"
|
|
:items="filteredItems"
|
|
:loading="loading"
|
|
multiple
|
|
label-key="name"
|
|
value-key="id"
|
|
icon="i-lucide-search"
|
|
placeholder="Buscar clientes por nombre o cédula..."
|
|
ignore-filter
|
|
>
|
|
<template #item-label="{ item }">
|
|
<div class="flex flex-col gap-0.5">
|
|
<span class="font-medium">{{ item.name }}</span>
|
|
<span v-if="item.cedula" class="text-xs text-muted">
|
|
{{ formatCedula(item.cedula) }}
|
|
</span>
|
|
</div>
|
|
</template>
|
|
|
|
<template #empty>
|
|
<div class="text-center py-2">
|
|
<span v-if="searchQuery.length < 4" class="text-muted text-sm">
|
|
Escribe al menos 4 caracteres para buscar
|
|
</span>
|
|
<span v-else class="text-muted text-sm">
|
|
No se encontraron clientes
|
|
</span>
|
|
</div>
|
|
</template>
|
|
</UInputMenu>
|
|
|
|
<!-- Selected count and clear all -->
|
|
<div v-if="selectedClientes.length > 0" class="flex items-center justify-between text-sm">
|
|
<span class="text-muted">
|
|
{{ selectedClientes.length }} cliente{{ selectedClientes.length !== 1 ? 's' : '' }} seleccionado{{ selectedClientes.length !== 1 ? 's' : '' }}
|
|
</span>
|
|
<UButton
|
|
size="xs"
|
|
color="gray"
|
|
variant="link"
|
|
@click="clearAll"
|
|
>
|
|
Limpiar todo
|
|
</UButton>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import type { InputMenuItem } from '@nuxt/ui'
|
|
|
|
interface Cliente {
|
|
id: number
|
|
name: string
|
|
cedula?: string
|
|
ubicacion?: string
|
|
}
|
|
|
|
const props = defineProps<{
|
|
selectedIds: number[]
|
|
}>()
|
|
|
|
const emit = defineEmits<{
|
|
'update:selectedIds': [value: number[]]
|
|
}>()
|
|
|
|
// State
|
|
const clientes = ref<Cliente[]>([])
|
|
const loading = ref(false)
|
|
const searchQuery = ref('')
|
|
|
|
// Computed - Sync with props
|
|
const selectedClientes = computed({
|
|
get: () => {
|
|
return clientes.value.filter(c => props.selectedIds.includes(c.id))
|
|
},
|
|
set: (value: Cliente[]) => {
|
|
emit('update:selectedIds', value.map(c => c.id))
|
|
}
|
|
})
|
|
|
|
// Computed - Filter items based on search (min 4 characters)
|
|
const filteredItems = computed((): InputMenuItem[] => {
|
|
const query = searchQuery.value.trim()
|
|
|
|
// Only show items if search has at least 4 characters
|
|
if (query.length < 4) {
|
|
return []
|
|
}
|
|
|
|
const lowerQuery = query.toLowerCase()
|
|
|
|
return clientes.value
|
|
.filter(c =>
|
|
c.name.toLowerCase().includes(lowerQuery) ||
|
|
c.cedula?.includes(query)
|
|
)
|
|
.map(c => ({
|
|
id: c.id,
|
|
name: c.name,
|
|
cedula: c.cedula,
|
|
label: c.name,
|
|
value: c.id
|
|
}))
|
|
})
|
|
|
|
// Methods
|
|
function clearAll() {
|
|
emit('update:selectedIds', [])
|
|
}
|
|
|
|
function formatCedula(cedula: string): string {
|
|
// Format as XXXX-XXXX-XXXXX
|
|
if (cedula.length === 13) {
|
|
return `${cedula.slice(0, 4)}-${cedula.slice(4, 8)}-${cedula.slice(8)}`
|
|
}
|
|
return cedula
|
|
}
|
|
|
|
async function loadClientes() {
|
|
loading.value = true
|
|
try {
|
|
const result = await $fetch('/api/clientes')
|
|
clientes.value = result as Cliente[]
|
|
} catch (error) {
|
|
console.error('[ClienteSelector] Error loading clientes:', error)
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
// Load on mount
|
|
onMounted(() => {
|
|
loadClientes()
|
|
})
|
|
</script>
|