Files
webapp/app/components/navigation/MainNavigation.vue
Ruslan Bakiev f34c484561
All checks were successful
Build Docker Image / build (push) Successful in 3m40s
Fix navigation layout and search behavior
- Reorder topnav components: search bar before submenu
- GlobalSearchBar: use page navigation instead of dropdowns
- Remove icons from MainNavigation and SubNavigation
- Create ListMapLayout universal component for list+map pages
- Migrate catalog pages (offers, suppliers, hubs) to ListMapLayout
2026-01-08 09:16:56 +07:00

196 lines
7.3 KiB
Vue

<template>
<header class="sticky top-0 z-40 bg-base-100 border-b border-base-300">
<div class="flex items-center justify-between h-16 px-4 lg:px-6">
<!-- Left: Logo -->
<div class="flex items-center">
<NuxtLink :to="localePath('/')" class="flex items-center gap-2">
<span class="font-bold text-xl">Optovia</span>
</NuxtLink>
</div>
<!-- Center: Main tabs -->
<nav class="hidden md:flex items-center gap-1">
<NuxtLink
v-for="tab in visibleTabs"
:key="tab.key"
:to="localePath(tab.path)"
class="px-4 py-2 rounded-full font-medium text-sm transition-colors hover:bg-base-200"
:class="{ 'bg-base-200 text-primary': isActiveTab(tab.key) }"
>
{{ tab.label }}
</NuxtLink>
</nav>
<!-- Right: Globe + Team + User -->
<div class="flex items-center gap-2">
<!-- Globe (language/currency) dropdown -->
<div class="dropdown dropdown-end">
<button tabindex="0" class="btn btn-ghost btn-circle">
<Icon name="lucide:globe" size="20" />
</button>
<div tabindex="0" class="dropdown-content bg-base-100 rounded-box z-50 w-52 p-4 shadow-lg border border-base-300">
<div class="font-semibold mb-2">{{ $t('common.language') }}</div>
<div class="flex gap-2 mb-4">
<NuxtLink
v-for="loc in locales"
:key="loc.code"
:to="switchLocalePath(loc.code)"
class="btn btn-sm"
:class="locale === loc.code ? 'btn-primary' : 'btn-ghost'"
>
{{ loc.code.toUpperCase() }}
</NuxtLink>
</div>
<div class="font-semibold mb-2">{{ $t('common.theme') }}</div>
<button
class="btn btn-sm btn-ghost w-full justify-start"
@click="$emit('toggle-theme')"
>
<Icon :name="theme === 'night' ? 'lucide:sun' : 'lucide:moon'" size="16" />
{{ theme === 'night' ? $t('common.theme_light') : $t('common.theme_dark') }}
</button>
</div>
</div>
<!-- Team dropdown -->
<template v-if="loggedIn && userData?.teams?.length">
<div class="dropdown dropdown-end">
<button tabindex="0" class="btn btn-ghost gap-2">
<Icon name="lucide:building-2" size="18" />
<span class="hidden sm:inline max-w-32 truncate">
{{ userData?.activeTeam?.name || $t('common.selectTeam') }}
</span>
<Icon name="lucide:chevron-down" size="14" />
</button>
<ul tabindex="0" class="dropdown-content menu bg-base-100 rounded-box z-50 w-56 p-2 shadow-lg border border-base-300">
<li class="menu-title"><span>{{ $t('common.teams') }}</span></li>
<li v-for="team in userData?.teams" :key="team.id">
<a
@click="$emit('switch-team', team)"
:class="{ active: team.id === userData?.activeTeamId }"
>
{{ team.name }}
</a>
</li>
<div class="divider my-1"></div>
<li>
<NuxtLink :to="localePath('/clientarea/team')">
<Icon name="lucide:settings" size="16" />
{{ $t('cabinetNav.teamSettings') }}
</NuxtLink>
</li>
</ul>
</div>
</template>
<!-- User menu -->
<template v-if="sessionChecked">
<template v-if="loggedIn">
<div class="dropdown dropdown-end">
<div tabindex="0" role="button" class="btn btn-ghost btn-circle avatar">
<div class="w-10 rounded-full">
<div v-if="userAvatarSvg" v-html="userAvatarSvg" class="w-full h-full" />
<div
v-else
class="w-full h-full bg-primary flex items-center justify-center text-primary-content font-bold text-sm"
>
{{ userInitials }}
</div>
</div>
</div>
<ul
tabindex="0"
class="dropdown-content menu bg-base-100 rounded-box z-50 w-52 p-2 shadow-lg border border-base-300"
>
<li class="menu-title">
<span>{{ userName }}</span>
</li>
<li>
<NuxtLink :to="localePath('/clientarea/profile')">
<Icon name="lucide:user" size="16" />
{{ $t('dashboard.profile') }}
</NuxtLink>
</li>
<div class="divider my-1"></div>
<li>
<a @click="$emit('sign-out')" class="text-error">
<Icon name="lucide:log-out" size="16" />
{{ $t('auth.logout') }}
</a>
</li>
</ul>
</div>
</template>
<template v-else>
<button @click="$emit('sign-in')" class="btn btn-primary btn-sm">
{{ $t('auth.login') }}
</button>
</template>
</template>
</div>
</div>
<!-- Mobile tabs (shown below header on small screens) -->
<nav class="md:hidden flex items-center justify-center gap-1 py-2 border-t border-base-300 overflow-x-auto">
<NuxtLink
v-for="tab in visibleTabs"
:key="tab.key"
:to="localePath(tab.path)"
class="px-4 py-2 rounded-full text-sm font-medium hover:bg-base-200"
:class="{ 'bg-base-200 text-primary': isActiveTab(tab.key) }"
>
{{ tab.label }}
</NuxtLink>
</nav>
</header>
</template>
<script setup lang="ts">
const props = defineProps<{
sessionChecked?: boolean
loggedIn?: boolean
userAvatarSvg?: string
userName?: string
userInitials?: string
theme?: 'default' | 'night'
userData?: {
id?: string
activeTeam?: { name?: string; teamType?: string }
activeTeamId?: string
teams?: Array<{ id?: string; name?: string; logtoOrgId?: string }>
} | null
isSeller?: boolean
}>()
defineEmits(['toggle-theme', 'sign-out', 'sign-in', 'switch-team'])
const localePath = useLocalePath()
const { locale, locales } = useI18n()
const switchLocalePath = useSwitchLocalePath()
const route = useRoute()
const { t } = useI18n()
const tabs = computed(() => [
{ key: 'catalog', label: t('cabinetNav.catalog'), path: '/catalog/offers', auth: false },
{ key: 'orders', label: t('cabinetNav.orders'), path: '/clientarea/orders', auth: true },
{ key: 'seller', label: t('cabinetNav.seller'), path: '/clientarea/offers', auth: true, seller: true },
])
const visibleTabs = computed(() => {
return tabs.value.filter(tab => {
if (tab.auth && !props.loggedIn) return false
if (tab.seller && !props.isSeller) return false
return true
})
})
const isActiveTab = (key: string) => {
const path = route.path
if (key === 'catalog') return path.startsWith('/catalog') || path === '/'
if (key === 'orders') return path.includes('/clientarea/orders') || path.includes('/clientarea/addresses') || path.includes('/clientarea/billing')
if (key === 'seller') return path.includes('/clientarea/offers')
return false
}
</script>