All checks were successful
Build Docker Image / build (push) Successful in 3m6s
- Add color scheme: product/offer=orange, supplier=blue, hub=green - Remove 'location' filter (same as hub) - Quantity filter appears only after product is selected - Map hover shows 'target' ring effect (outer white ring) - Tokens in header use entity-specific colors
247 lines
6.6 KiB
TypeScript
247 lines
6.6 KiB
TypeScript
import type { LocationQuery } from 'vue-router'
|
|
|
|
export type SelectMode = 'product' | 'supplier' | 'hub' | null
|
|
export type DisplayMode =
|
|
| 'hero'
|
|
| 'grid-products'
|
|
| 'grid-suppliers'
|
|
| 'grid-hubs'
|
|
| 'grid-hubs-for-product'
|
|
| 'grid-products-from-supplier'
|
|
| 'grid-products-in-hub'
|
|
| 'grid-offers'
|
|
|
|
export interface SearchFilter {
|
|
type: 'product' | 'supplier' | 'hub' | 'quantity'
|
|
id: string
|
|
label: string
|
|
}
|
|
|
|
export interface SearchState {
|
|
selectMode: SelectMode
|
|
product: { id: string; name: string } | null
|
|
supplier: { id: string; name: string } | null
|
|
hub: { id: string; name: string } | null
|
|
quantity: string | null
|
|
}
|
|
|
|
// Color scheme for entity types
|
|
export const entityColors = {
|
|
product: '#f97316', // orange
|
|
supplier: '#3b82f6', // blue
|
|
hub: '#22c55e', // green
|
|
offer: '#f97316' // orange (same as product context)
|
|
} as const
|
|
|
|
// Filter labels cache (to show names instead of UUIDs)
|
|
const filterLabels = ref<Record<string, Record<string, string>>>({
|
|
product: {},
|
|
supplier: {},
|
|
hub: {}
|
|
})
|
|
|
|
export function useCatalogSearch() {
|
|
const route = useRoute()
|
|
const router = useRouter()
|
|
const { t } = useI18n()
|
|
|
|
// Parse current state from query params
|
|
const selectMode = computed<SelectMode>(() => {
|
|
const select = route.query.select as string | undefined
|
|
if (select === 'product' || select === 'supplier' || select === 'hub') {
|
|
return select
|
|
}
|
|
return null
|
|
})
|
|
|
|
const productId = computed(() => route.query.product as string | undefined)
|
|
const supplierId = computed(() => route.query.supplier as string | undefined)
|
|
const hubId = computed(() => route.query.hub as string | undefined)
|
|
const quantity = computed(() => route.query.qty as string | undefined)
|
|
|
|
// Get label for a filter (from cache or fallback to ID)
|
|
const getLabel = (type: string, id: string | undefined): string | null => {
|
|
if (!id) return null
|
|
return filterLabels.value[type]?.[id] || id.slice(0, 8) + '...'
|
|
}
|
|
|
|
// Set label in cache
|
|
const setLabel = (type: string, id: string, label: string) => {
|
|
if (!filterLabels.value[type]) {
|
|
filterLabels.value[type] = {}
|
|
}
|
|
filterLabels.value[type][id] = label
|
|
}
|
|
|
|
// Active tokens for display
|
|
const activeTokens = computed(() => {
|
|
const tokens: Array<{ type: string; id: string; label: string; icon: string }> = []
|
|
|
|
if (productId.value) {
|
|
tokens.push({
|
|
type: 'product',
|
|
id: productId.value,
|
|
label: getLabel('product', productId.value) || t('catalog.filters.product'),
|
|
icon: 'lucide:package'
|
|
})
|
|
}
|
|
if (supplierId.value) {
|
|
tokens.push({
|
|
type: 'supplier',
|
|
id: supplierId.value,
|
|
label: getLabel('supplier', supplierId.value) || t('catalog.filters.supplier'),
|
|
icon: 'lucide:factory'
|
|
})
|
|
}
|
|
if (hubId.value) {
|
|
tokens.push({
|
|
type: 'hub',
|
|
id: hubId.value,
|
|
label: getLabel('hub', hubId.value) || t('catalog.filters.hub'),
|
|
icon: 'lucide:map-pin'
|
|
})
|
|
}
|
|
if (quantity.value) {
|
|
tokens.push({
|
|
type: 'quantity',
|
|
id: quantity.value,
|
|
label: `${quantity.value} т`,
|
|
icon: 'lucide:scale'
|
|
})
|
|
}
|
|
|
|
return tokens
|
|
})
|
|
|
|
// Available chips (filters not yet set)
|
|
const availableChips = computed(() => {
|
|
const chips: Array<{ type: string; label: string }> = []
|
|
|
|
if (!productId.value && selectMode.value !== 'product') {
|
|
chips.push({ type: 'product', label: t('catalog.filters.product') })
|
|
}
|
|
if (!supplierId.value && selectMode.value !== 'supplier') {
|
|
chips.push({ type: 'supplier', label: t('catalog.filters.supplier') })
|
|
}
|
|
if (!hubId.value && selectMode.value !== 'hub') {
|
|
chips.push({ type: 'hub', label: t('catalog.filters.hub') })
|
|
}
|
|
// Quantity only available after product is selected
|
|
if (productId.value && !quantity.value) {
|
|
chips.push({ type: 'quantity', label: t('catalog.filters.quantity') })
|
|
}
|
|
|
|
return chips
|
|
})
|
|
|
|
// Determine what content to show
|
|
const displayMode = computed<DisplayMode>(() => {
|
|
// Selection mode takes priority
|
|
if (selectMode.value === 'product') return 'grid-products'
|
|
if (selectMode.value === 'supplier') return 'grid-suppliers'
|
|
if (selectMode.value === 'hub') return 'grid-hubs'
|
|
|
|
// Results based on filters
|
|
if (productId.value && hubId.value) return 'grid-offers'
|
|
if (supplierId.value && productId.value) return 'grid-offers'
|
|
if (productId.value) return 'grid-hubs-for-product'
|
|
if (supplierId.value) return 'grid-products-from-supplier'
|
|
if (hubId.value) return 'grid-products-in-hub'
|
|
|
|
// Empty state
|
|
return 'hero'
|
|
})
|
|
|
|
// Check if we're on the main page (not /catalog)
|
|
const localePath = useLocalePath()
|
|
const isMainPage = computed(() => {
|
|
const catalogPath = localePath('/catalog')
|
|
return !route.path.startsWith(catalogPath)
|
|
})
|
|
|
|
// Navigation helpers
|
|
const updateQuery = (updates: Partial<LocationQuery>) => {
|
|
const newQuery = { ...route.query }
|
|
|
|
Object.entries(updates).forEach(([key, value]) => {
|
|
if (value === null || value === undefined) {
|
|
delete newQuery[key]
|
|
} else {
|
|
newQuery[key] = value as string
|
|
}
|
|
})
|
|
|
|
// If on main page and adding filters, navigate to /catalog
|
|
if (isMainPage.value && Object.keys(newQuery).length > 0) {
|
|
router.push({ path: localePath('/catalog'), query: newQuery })
|
|
} else {
|
|
router.push({ query: newQuery })
|
|
}
|
|
}
|
|
|
|
const startSelect = (type: SelectMode) => {
|
|
updateQuery({ select: type })
|
|
}
|
|
|
|
const cancelSelect = () => {
|
|
updateQuery({ select: null })
|
|
}
|
|
|
|
const selectItem = (type: string, id: string, label: string) => {
|
|
setLabel(type, id, label)
|
|
updateQuery({
|
|
[type]: id,
|
|
select: null // Exit selection mode
|
|
})
|
|
}
|
|
|
|
const removeFilter = (type: string) => {
|
|
updateQuery({ [type]: null })
|
|
}
|
|
|
|
const editFilter = (type: string) => {
|
|
updateQuery({ select: type as SelectMode })
|
|
}
|
|
|
|
const clearAll = () => {
|
|
if (isMainPage.value) {
|
|
router.push({ path: localePath('/catalog'), query: {} })
|
|
} else {
|
|
router.push({ query: {} })
|
|
}
|
|
}
|
|
|
|
// Text search (for filtering within current grid) - shared via useState
|
|
const searchQuery = useState<string>('catalog-search-query', () => '')
|
|
|
|
return {
|
|
// State
|
|
selectMode,
|
|
displayMode,
|
|
productId,
|
|
supplierId,
|
|
hubId,
|
|
quantity,
|
|
searchQuery,
|
|
|
|
// Colors
|
|
entityColors,
|
|
|
|
// Computed
|
|
activeTokens,
|
|
availableChips,
|
|
|
|
// Actions
|
|
startSelect,
|
|
cancelSelect,
|
|
selectItem,
|
|
removeFilter,
|
|
editFilter,
|
|
clearAll,
|
|
setLabel,
|
|
|
|
// Labels cache
|
|
filterLabels
|
|
}
|
|
}
|