fc3e: PostsFilterBar — artist autocomplete + platform v-select with URL-driven hydration
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
<template>
|
||||
<div class="fc-posts-filters">
|
||||
<v-autocomplete
|
||||
v-model="artistModel"
|
||||
:items="artistOptions"
|
||||
:loading="artistLoading"
|
||||
:search="artistQuery"
|
||||
item-title="name"
|
||||
item-value="id"
|
||||
label="Artist"
|
||||
density="compact"
|
||||
hide-details
|
||||
clearable
|
||||
no-filter
|
||||
return-object
|
||||
style="min-width: 240px"
|
||||
@update:search="onArtistSearch"
|
||||
@update:model-value="onArtistPicked"
|
||||
/>
|
||||
|
||||
<v-select
|
||||
v-model="platformModel"
|
||||
:items="platformItems"
|
||||
label="Platform"
|
||||
density="compact"
|
||||
hide-details
|
||||
clearable
|
||||
style="min-width: 180px"
|
||||
@update:model-value="emitFilters"
|
||||
/>
|
||||
|
||||
<v-btn
|
||||
v-if="hasFilters"
|
||||
variant="text" size="small"
|
||||
@click="clearAll"
|
||||
>Clear</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { useSourcesStore } from '../../stores/sources.js'
|
||||
import { usePlatformsStore } from '../../stores/platforms.js'
|
||||
|
||||
const props = defineProps({
|
||||
artistId: { type: Number, default: null },
|
||||
platform: { type: String, default: null },
|
||||
})
|
||||
const emit = defineEmits(['update:filters'])
|
||||
|
||||
const sourcesStore = useSourcesStore()
|
||||
const platformsStore = usePlatformsStore()
|
||||
|
||||
// Artist autocomplete state
|
||||
const artistQuery = ref('')
|
||||
const artistOptions = ref([])
|
||||
const artistLoading = ref(false)
|
||||
const artistModel = ref(null)
|
||||
|
||||
// Platform v-select state
|
||||
const platformItems = computed(() =>
|
||||
(platformsStore.platforms || []).map(p => ({ title: p.key, value: p.key }))
|
||||
)
|
||||
const platformModel = ref(props.platform)
|
||||
|
||||
const hasFilters = computed(
|
||||
() => artistModel.value != null || platformModel.value != null
|
||||
)
|
||||
|
||||
let _searchTimer = null
|
||||
function onArtistSearch(q) {
|
||||
artistQuery.value = q
|
||||
clearTimeout(_searchTimer)
|
||||
if (!q || !q.trim()) {
|
||||
artistOptions.value = artistModel.value ? [artistModel.value] : []
|
||||
return
|
||||
}
|
||||
_searchTimer = setTimeout(async () => {
|
||||
artistLoading.value = true
|
||||
try {
|
||||
const results = await sourcesStore.autocompleteArtist(q, 20)
|
||||
artistOptions.value = results
|
||||
} finally {
|
||||
artistLoading.value = false
|
||||
}
|
||||
}, 200)
|
||||
}
|
||||
|
||||
function onArtistPicked() {
|
||||
emitFilters()
|
||||
}
|
||||
|
||||
function emitFilters() {
|
||||
emit('update:filters', {
|
||||
artist_id: artistModel.value?.id ?? null,
|
||||
platform: platformModel.value || null,
|
||||
})
|
||||
}
|
||||
|
||||
function clearAll() {
|
||||
artistModel.value = null
|
||||
platformModel.value = null
|
||||
artistOptions.value = []
|
||||
artistQuery.value = ''
|
||||
emitFilters()
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await platformsStore.loadAll()
|
||||
// If a deep-link arrives with an artist_id, hydrate the selected display.
|
||||
if (props.artistId != null) {
|
||||
const seed = { id: props.artistId, name: `Artist #${props.artistId}` }
|
||||
artistModel.value = seed
|
||||
artistOptions.value = [seed]
|
||||
}
|
||||
})
|
||||
|
||||
watch(() => props.artistId, (val) => {
|
||||
if (val == null) {
|
||||
artistModel.value = null
|
||||
} else if (artistModel.value?.id !== val) {
|
||||
const seed = { id: val, name: `Artist #${val}` }
|
||||
artistModel.value = seed
|
||||
artistOptions.value = [seed]
|
||||
}
|
||||
})
|
||||
|
||||
watch(() => props.platform, (val) => {
|
||||
if (platformModel.value !== val) platformModel.value = val || null
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.fc-posts-filters {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
align-items: center;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user