Files
webapp/app/components/search/GlobalSearchBar.vue
Ruslan Bakiev 08d7e0ade9
All checks were successful
Build Docker Image / build (push) Successful in 3m23s
Implement unified catalog search with token-based filtering
- Add useCatalogSearch composable for managing unified search state
- Add UnifiedSearchBar component with token chips for filters
- Add CatalogHero component for empty/landing state
- Create grid components for each display mode:
  - CatalogGridProducts, CatalogGridSuppliers, CatalogGridHubs
  - CatalogGridHubsForProduct, CatalogGridProductsFromSupplier
  - CatalogGridProductsInHub, CatalogGridOffers
- Add unified catalog page at /catalog with query params
- Remove SubNavigation from catalog section (kept for other sections)
- Update all links to use new unified catalog paths
- Delete old nested catalog pages (offers/suppliers/hubs flows)
- Add i18n translations for catalog section
2026-01-22 10:57:30 +07:00

144 lines
4.5 KiB
Vue

<template>
<div class="bg-base-100 py-4 px-4 lg:px-6">
<div class="flex items-center justify-center">
<form
@submit.prevent="handleSearch"
class="flex items-center bg-base-100 rounded-full border border-base-300 shadow-sm hover:shadow-md transition-shadow"
>
<!-- Product field (clickable, navigates to /goods) -->
<div
class="flex flex-col px-4 py-2 min-w-48 pl-6 rounded-l-full hover:bg-base-200/50 border-r border-base-300 cursor-pointer"
@click="goToProductSelection"
>
<label class="text-xs font-semibold text-base-content/60 mb-0.5">
{{ $t('search.product') }}
</label>
<div class="text-sm" :class="productDisplay ? 'text-base-content' : 'text-base-content/50'">
{{ productDisplay || $t('search.product_placeholder') }}
</div>
</div>
<!-- Quantity field (editable) -->
<div class="flex flex-col px-4 py-2 min-w-48 hover:bg-base-200/50 border-r border-base-300">
<label class="text-xs font-semibold text-base-content/60 mb-0.5">
{{ $t('search.quantity') }}
</label>
<input
v-model="quantity"
type="number"
min="1"
:placeholder="$t('search.quantity_placeholder')"
class="w-full bg-transparent outline-none text-sm"
@change="syncQuantityToStore"
/>
</div>
<!-- Destination field (clickable, navigates to /select-location) -->
<div
class="flex flex-col px-4 py-2 min-w-48 hover:bg-base-200/50 cursor-pointer"
@click="goToLocationSelection"
>
<label class="text-xs font-semibold text-base-content/60 mb-0.5">
{{ $t('search.destination') }}
</label>
<div class="text-sm" :class="locationDisplay ? 'text-base-content' : 'text-base-content/50'">
{{ locationDisplay || $t('search.destination_placeholder') }}
</div>
</div>
<!-- Search button -->
<button
type="submit"
class="btn btn-primary btn-circle ml-2 mr-1"
:disabled="!canSearch"
>
<Icon name="lucide:search" size="18" />
</button>
</form>
</div>
</div>
</template>
<script setup lang="ts">
const emit = defineEmits<{
search: [params: { productUuid?: string; quantity?: number; locationUuid?: string }]
}>()
const router = useRouter()
const localePath = useLocalePath()
const searchStore = useSearchStore()
// Read from searchStore
const productDisplay = computed(() => searchStore.searchForm.product || '')
const productUuid = computed(() => searchStore.searchForm.productUuid || '')
const locationDisplay = computed(() => searchStore.searchForm.location || '')
const locationUuid = computed(() => searchStore.searchForm.locationUuid || '')
// Quantity - local state synced with store
const quantity = ref<number | undefined>(
searchStore.searchForm.quantity ? Number(searchStore.searchForm.quantity) : undefined
)
const syncQuantityToStore = () => {
if (quantity.value) {
searchStore.setQuantity(String(quantity.value))
}
}
// Navigation to selection pages
const goToProductSelection = () => {
navigateTo(localePath('/goods'))
}
const goToLocationSelection = () => {
navigateTo(localePath('/select-location') + '?mode=search')
}
// Can search - need at least product selected
const canSearch = computed(() => {
return !!productUuid.value
})
// Search handler - navigate to catalog offers flow
const handleSearch = () => {
if (!canSearch.value) return
// Sync quantity to store
syncQuantityToStore()
// Build query params
const query: Record<string, string> = {}
if (quantity.value) {
query.quantity = String(quantity.value)
}
// Navigate to unified catalog
if (productUuid.value && locationUuid.value) {
// Both product and hub selected -> show offers
router.push({
path: localePath('/catalog'),
query: { product: productUuid.value, hub: locationUuid.value, ...query }
})
} else if (productUuid.value) {
// Only product selected -> show hubs for product
router.push({
path: localePath('/catalog'),
query: { product: productUuid.value, ...query }
})
}
emit('search', {
productUuid: productUuid.value,
quantity: quantity.value,
locationUuid: locationUuid.value
})
}
// Watch store changes to sync quantity
watch(() => searchStore.searchForm.quantity, (val) => {
if (val) {
quantity.value = Number(val)
}
}, { immediate: true })
</script>