Files
webapp/app/components/catalog/MapHero.vue
2026-01-07 09:10:35 +07:00

115 lines
3.1 KiB
Vue

<template>
<div class="relative h-80 sm:h-96 overflow-hidden rounded-2xl border border-base-300 bg-base-200">
<!-- Map -->
<ClientOnly>
<MapboxGlobe
v-if="hasCoordinates"
:key="mapKey"
:map-id="`hero-${mapKey}`"
:locations="[mapLocation]"
height="100%"
:initial-center="mapCenter"
:initial-zoom="initialZoom"
/>
<div v-else class="w-full h-full bg-gradient-to-br from-primary/20 via-primary/10 to-base-200" />
</ClientOnly>
<!-- Overlay -->
<div class="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/80 to-transparent p-6 sm:p-8">
<Stack gap="3">
<!-- Title -->
<h1 class="text-2xl sm:text-3xl font-bold text-white">{{ title }}</h1>
<p v-if="location?.country" class="text-white/80 flex items-center gap-2">
<span class="text-xl leading-none">{{ countryFlag }}</span>
<span>{{ location.country }}</span>
</p>
<!-- Badges -->
<div v-if="badges.length > 0" class="flex flex-wrap items-center gap-2">
<span
v-for="(badge, index) in badges"
:key="index"
class="badge badge-dash"
:class="badgeTone(badge, index)"
>
<Icon v-if="badge.icon" :name="badge.icon" size="14" />
{{ badge.text }}
</span>
</div>
</Stack>
</div>
</div>
</template>
<script setup lang="ts">
interface Badge {
icon?: string
text: string
tone?: string
}
interface Location {
uuid?: string | null
name?: string | null
latitude?: number | null
longitude?: number | null
country?: string | null
countryCode?: string | null
}
const props = withDefaults(defineProps<{
title: string
badges?: Badge[]
location?: Location | null
initialZoom?: number
}>(), {
badges: () => [],
location: null,
initialZoom: 6
})
const hasCoordinates = computed(() =>
props.location?.latitude != null && props.location?.longitude != null
)
// Key to force map recreation when location changes
const mapKey = computed(() =>
`${props.location?.uuid}-${props.location?.latitude}-${props.location?.longitude}`
)
const mapCenter = computed<[number, number]>(() => {
if (hasCoordinates.value) {
return [props.location!.longitude!, props.location!.latitude!]
}
return [37.64, 55.76]
})
const mapLocation = computed(() => ({
uuid: props.location?.uuid,
name: props.location?.name,
latitude: props.location?.latitude,
longitude: props.location?.longitude,
country: props.location?.country
}))
const badgeTone = (badge: Badge, index: number) => {
if (badge.tone) return `badge-${badge.tone}`
const palette = ['badge-primary', 'badge-secondary', 'badge-accent', 'badge-info', 'badge-success']
return palette[index % palette.length]
}
const isoToEmoji = (code: string): string => {
return code.toUpperCase().split('').map(char => String.fromCodePoint(0x1F1E6 - 65 + char.charCodeAt(0))).join('')
}
const countryFlag = computed(() => {
if (props.location?.countryCode) {
return isoToEmoji(props.location.countryCode)
}
if (props.location?.country) {
return '🌍'
}
return ''
})
</script>