Files
webapp/app/components/catalog/HubCard.vue
Ruslan Bakiev 1e87a14065
All checks were successful
Build Docker Image / build (push) Successful in 4m49s
feat(catalog): implement step-by-step navigation for offers and suppliers
- Transform offers/index.vue to show products list with sparkline charts
- Create nested routes for offers: /offers → /offers/[productId] → /offers/[productId]/[hubId]
- Create nested routes for suppliers: /suppliers → /suppliers/[supplierId] → /suppliers/[supplierId]/[productId] → /suppliers/[supplierId]/[productId]/[hubId]
- Add OffersBreadcrumbs and SuppliersBreadcrumbs components for navigation
- Update HubCard to accept custom linkTo prop
- Key difference: Suppliers calculation uses FindRoutes (single source), Offers uses FindProductRoutes (all sources)
2026-01-16 00:52:47 +07:00

76 lines
2.0 KiB
Vue

<template>
<component
:is="linkable ? NuxtLink : 'div'"
:to="linkable ? resolvedLink : undefined"
class="block"
:class="{ 'cursor-pointer': selectable }"
@click="selectable && $emit('select')"
@mouseenter="$emit('hover', true)"
@mouseleave="$emit('hover', false)"
>
<Card
padding="small"
:interactive="linkable || selectable"
:class="[
isSelected && 'ring-2 ring-primary ring-offset-2'
]"
>
<div class="flex flex-col gap-1">
<!-- Title -->
<Text size="base" weight="semibold" class="truncate">{{ hub.name }}</Text>
<!-- Country left, distance right -->
<div class="flex items-center justify-between">
<Text tone="muted" size="sm">
{{ countryFlag }} {{ hub.country || t('catalogMap.labels.country_unknown') }}
</Text>
<span v-if="hub.distance" class="badge badge-neutral badge-dash text-xs">
{{ hub.distance }}
</span>
</div>
</div>
</Card>
</component>
</template>
<script setup lang="ts">
import { NuxtLink } from '#components'
interface Hub {
uuid?: string | null
name?: string | null
country?: string | null
countryCode?: string | null
distance?: string
}
const props = defineProps<{
hub: Hub
selectable?: boolean
isSelected?: boolean
linkTo?: string
}>()
defineEmits<{
(e: 'select'): void
(e: 'hover', hovered: boolean): void
}>()
const localePath = useLocalePath()
const { t } = useI18n()
const linkable = computed(() => !props.selectable && (props.linkTo || props.hub.uuid))
const resolvedLink = computed(() => props.linkTo || localePath(`/catalog/hubs/${props.hub.uuid}`))
// ISO code to emoji flag
const isoToEmoji = (code: string): string => {
return code.toUpperCase().split('').map(char => String.fromCodePoint(0x1F1E6 - 65 + char.charCodeAt(0))).join('')
}
const countryFlag = computed(() => {
if (props.hub.countryCode) {
return isoToEmoji(props.hub.countryCode)
}
return '🌍'
})
</script>