From 86ff97bc824bf7652f9c3de3a6302c0479f62651 Mon Sep 17 00:00:00 2001 From: neon <18559658+neonsolstice@users.noreply.github.com> Date: Thu, 15 Jan 2026 05:38:45 -0600 Subject: [PATCH] feat(client): add responsive header with global search and filter bar --- client/src/components/AppHeader.vue | 418 +++++++++++++----- .../book/composables/useGlobalSearch.ts | 46 ++ client/src/views/HomeView.vue | 15 +- server/src/db/migrations/meta/_journal.json | 2 +- server/src/modules/book/book.controller.ts | 7 + server/src/modules/book/book.repository.ts | 47 +- server/src/modules/book/book.service.ts | 6 + .../src/modules/book/dto/search-books.dto.ts | 15 + 8 files changed, 448 insertions(+), 108 deletions(-) create mode 100644 client/src/features/book/composables/useGlobalSearch.ts create mode 100644 server/src/modules/book/dto/search-books.dto.ts diff --git a/client/src/components/AppHeader.vue b/client/src/components/AppHeader.vue index 964803daf..7516fffac 100644 --- a/client/src/components/AppHeader.vue +++ b/client/src/components/AppHeader.vue @@ -1,153 +1,361 @@ diff --git a/client/src/features/book/composables/useGlobalSearch.ts b/client/src/features/book/composables/useGlobalSearch.ts new file mode 100644 index 000000000..ab7477460 --- /dev/null +++ b/client/src/features/book/composables/useGlobalSearch.ts @@ -0,0 +1,46 @@ +import { ref, watch, type Ref } from 'vue' +import { api } from '@/lib/api' + +export interface GlobalSearchResult { + id: number + title: string | null + authors: string[] + libraryId: number + libraryName: string +} + +export function useGlobalSearch(query: Ref) { + const results = ref([]) + const loading = ref(false) + let timer: ReturnType | null = null + + watch(query, (q) => { + if (timer) clearTimeout(timer) + if (q.trim().length < 2) { + results.value = [] + loading.value = false + return + } + loading.value = true + timer = setTimeout(async () => { + try { + const params = new URLSearchParams({ q: q.trim(), limit: '10' }) + const res = await api(`/api/books/search?${params}`) + if (!res.ok) throw new Error(`HTTP ${res.status}`) + results.value = await res.json() + } catch { + results.value = [] + } finally { + loading.value = false + } + }, 300) + }) + + function clear() { + if (timer) clearTimeout(timer) + results.value = [] + loading.value = false + } + + return { results, loading, clear } +} diff --git a/client/src/views/HomeView.vue b/client/src/views/HomeView.vue index dad9739c7..9a47d431a 100644 --- a/client/src/views/HomeView.vue +++ b/client/src/views/HomeView.vue @@ -1,6 +1,7 @@