Files
analiticaNucleo/nuxt4-app/app/components/ClienteMultiSelector.vue
josedario87 425e828083
Some checks failed
build-and-deploy / build-and-deploy (push) Has been cancelled
Style: Aplicar tema café/dorado personalizado al InputMenu
Colores aplicados según el sistema de diseño de la app:
- Fondo del input: --brand-surface (#1f180f)
- Borde: --brand-border (#3a2a16)
- Texto: --brand-text (#fef9f0)
- Placeholder y iconos: --brand-text-muted (#d8c7a6)
- Dropdown: mismo fondo y borde que el input
- Item highlighted: rgba(224,192,128,0.12) (tono dorado suave)
- Tags seleccionados: rgba(224,192,128,0.14) con borde más fuerte
- Color de tags: --brand-primary (#e0c080)
- Hover en botón delete: tono dorado más intenso

El componente ahora tiene la misma paleta de colores que las cards
de Metabase Debug y el resto de la aplicación, eliminando los
colores azules/verdes por defecto de Nuxt UI.
2025-10-30 13:26:48 -06:00

164 lines
4.5 KiB
Vue

<template>
<div class="space-y-3">
<!-- InputMenu for cliente search and selection -->
<UInputMenu
:model-value="selectedClientesObjects"
:items="filteredItems"
:loading="loading"
multiple
label-key="name"
value-key="id"
icon="i-lucide-search"
placeholder="Buscar clientes por nombre o cédula..."
size="sm"
ignore-filter
:ui="{
base: 'bg-[var(--brand-surface)] text-[var(--brand-text)] border border-[var(--brand-border)]',
placeholder: 'placeholder-[var(--brand-text-muted)]',
leadingIcon: 'text-[var(--brand-text-muted)]',
content: 'bg-[var(--brand-surface)] border border-[var(--brand-border)]',
item: 'text-[var(--brand-text)] data-highlighted:bg-[rgba(224,192,128,0.12)] data-highlighted:text-[var(--brand-text)]',
itemLeadingIcon: 'text-[var(--brand-text-muted)]',
tagsItem: 'bg-[rgba(224,192,128,0.14)] border border-[rgba(224,192,128,0.28)] text-[var(--brand-primary)]',
tagsItemText: 'text-[var(--brand-primary)]',
tagsItemDelete: 'text-[var(--brand-text-muted)] hover:text-[var(--brand-primary)] hover:bg-[rgba(224,192,128,0.2)]'
}"
@update:model-value="onSelectionChange"
@update:search-term="searchQuery = $event"
>
<template #item-label="{ item }">
<div class="flex flex-col gap-0.5">
<span class="font-medium text-[var(--brand-text)]">{{ item.name }}</span>
<span v-if="item.cedula" class="text-xs text-[var(--brand-text-muted)]">
{{ formatCedula(item.cedula) }}
</span>
</div>
</template>
<template #empty>
<div class="text-center py-2">
<span v-if="searchQuery.length < 4" class="text-[var(--brand-text-muted)] text-sm">
Escribe al menos 4 caracteres para buscar
</span>
<span v-else class="text-[var(--brand-text-muted)] text-sm">
No se encontraron clientes
</span>
</div>
</template>
</UInputMenu>
<!-- Selected count and clear all -->
<div v-if="selectedIds.length > 0" class="flex items-center justify-between text-sm">
<span class="text-[var(--brand-text-muted)]">
{{ selectedIds.length }} cliente{{ selectedIds.length !== 1 ? 's' : '' }} seleccionado{{ selectedIds.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 - Get selected clientes as objects for InputMenu
const selectedClientesObjects = computed(() => {
return clientes.value
.filter(c => props.selectedIds.includes(c.id))
.map(c => ({
id: c.id,
name: c.name,
cedula: c.cedula,
label: c.name,
value: 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 onSelectionChange(value: any[]) {
// Extract IDs from selected items
const ids = value.map(item => item.id || item.value || item)
emit('update:selectedIds', ids)
}
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>