173 lines
5.0 KiB
Vue
173 lines
5.0 KiB
Vue
<script setup lang="ts">
|
|
import { ref, computed, watch, onMounted } from 'vue'
|
|
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
|
import { Button } from '@/components/ui/button'
|
|
import { Input } from '@/components/ui/input'
|
|
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from '@/components/ui/command'
|
|
import { Check, ChevronsUpDown, X } from 'lucide-vue-next'
|
|
import { cn } from '@/lib/utils'
|
|
import type { FieldConfig } from '@/types/field-types'
|
|
|
|
interface Props {
|
|
field: FieldConfig
|
|
modelValue: string | null // The ID of the selected record
|
|
readonly?: boolean
|
|
baseUrl?: string // Base API URL, defaults to '/central'
|
|
}
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
// Default to runtime objects endpoint; override when consuming central entities
|
|
baseUrl: '/runtime/objects',
|
|
modelValue: null,
|
|
})
|
|
|
|
const emit = defineEmits<{
|
|
'update:modelValue': [value: string | null]
|
|
}>()
|
|
|
|
const { api } = useApi()
|
|
const open = ref(false)
|
|
const searchQuery = ref('')
|
|
const records = ref<any[]>([])
|
|
const loading = ref(false)
|
|
const selectedRecord = ref<any | null>(null)
|
|
|
|
// Get the relation configuration
|
|
const relationObject = computed(() => props.field.relationObject || props.field.apiName.replace('Id', ''))
|
|
const displayField = computed(() => props.field.relationDisplayField || 'name')
|
|
|
|
// Display value for the selected record
|
|
const displayValue = computed(() => {
|
|
if (!selectedRecord.value) return 'Select...'
|
|
return selectedRecord.value[displayField.value] || selectedRecord.value.id
|
|
})
|
|
|
|
// Filtered records based on search
|
|
const filteredRecords = computed(() => {
|
|
if (!searchQuery.value) return records.value
|
|
|
|
const query = searchQuery.value.toLowerCase()
|
|
return records.value.filter(record => {
|
|
const displayValue = record[displayField.value] || record.id
|
|
return displayValue.toLowerCase().includes(query)
|
|
})
|
|
})
|
|
|
|
// Fetch available records for the lookup
|
|
const fetchRecords = async () => {
|
|
loading.value = true
|
|
try {
|
|
const endpoint = `${props.baseUrl}/${relationObject.value}/records`
|
|
const response = await api.get(endpoint)
|
|
records.value = response || []
|
|
|
|
// If we have a modelValue, find the selected record
|
|
if (props.modelValue) {
|
|
selectedRecord.value = records.value.find(r => r.id === props.modelValue) || null
|
|
}
|
|
} catch (err) {
|
|
console.error('Error fetching lookup records:', err)
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
// Handle record selection
|
|
const selectRecord = (record: any) => {
|
|
selectedRecord.value = record
|
|
emit('update:modelValue', record.id)
|
|
open.value = false
|
|
}
|
|
|
|
// Clear selection
|
|
const clearSelection = () => {
|
|
selectedRecord.value = null
|
|
emit('update:modelValue', null)
|
|
}
|
|
|
|
// Watch for external modelValue changes
|
|
watch(() => props.modelValue, (newValue) => {
|
|
if (newValue && records.value.length > 0) {
|
|
selectedRecord.value = records.value.find(r => r.id === newValue) || null
|
|
} else if (!newValue) {
|
|
selectedRecord.value = null
|
|
}
|
|
})
|
|
|
|
onMounted(() => {
|
|
fetchRecords()
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div class="lookup-field space-y-2">
|
|
<Popover v-model:open="open">
|
|
<div class="flex gap-2">
|
|
<PopoverTrigger as-child>
|
|
<Button
|
|
variant="outline"
|
|
role="combobox"
|
|
:aria-expanded="open"
|
|
:disabled="readonly || loading"
|
|
class="flex-1 justify-between"
|
|
>
|
|
<span class="truncate">{{ displayValue }}</span>
|
|
<ChevronsUpDown class="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
|
</Button>
|
|
</PopoverTrigger>
|
|
|
|
<Button
|
|
v-if="selectedRecord && !readonly"
|
|
variant="outline"
|
|
size="icon"
|
|
@click="clearSelection"
|
|
class="shrink-0"
|
|
>
|
|
<X class="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
|
|
<PopoverContent class="w-[400px] p-0">
|
|
<Command>
|
|
<CommandInput
|
|
v-model="searchQuery"
|
|
placeholder="Search..."
|
|
/>
|
|
<CommandEmpty>
|
|
{{ loading ? 'Loading...' : 'No results found.' }}
|
|
</CommandEmpty>
|
|
<CommandList>
|
|
<CommandGroup>
|
|
<CommandItem
|
|
v-for="record in filteredRecords"
|
|
:key="record.id"
|
|
:value="record.id"
|
|
@select="selectRecord(record)"
|
|
>
|
|
<Check
|
|
:class="cn(
|
|
'mr-2 h-4 w-4',
|
|
selectedRecord?.id === record.id ? 'opacity-100' : 'opacity-0'
|
|
)"
|
|
/>
|
|
{{ record[displayField] || record.id }}
|
|
</CommandItem>
|
|
</CommandGroup>
|
|
</CommandList>
|
|
</Command>
|
|
</PopoverContent>
|
|
</Popover>
|
|
|
|
<!-- Display readonly value -->
|
|
<div v-if="readonly && selectedRecord" class="text-sm text-muted-foreground">
|
|
{{ selectedRecord[displayField] || selectedRecord.id }}
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.lookup-field {
|
|
width: 100%;
|
|
}
|
|
</style>
|