396 lines
14 KiB
Vue
396 lines
14 KiB
Vue
<template>
|
|
<aside
|
|
class="bg-base-100 h-screen flex flex-col border-r border-base-300 transition-all duration-300"
|
|
:class="collapsed ? 'w-16' : 'w-64'"
|
|
>
|
|
<!-- Logo + Collapse button -->
|
|
<div class="p-4 border-b border-base-300 flex items-center justify-between">
|
|
<NuxtLink v-if="!collapsed" :to="localePath('/')" class="text-2xl font-bold text-primary">
|
|
Optovia
|
|
</NuxtLink>
|
|
<NuxtLink v-else :to="localePath('/')" class="text-xl font-bold text-primary">
|
|
O
|
|
</NuxtLink>
|
|
<button
|
|
@click="collapsed = !collapsed"
|
|
class="btn btn-ghost btn-xs btn-square"
|
|
:title="collapsed ? 'Expand' : 'Collapse'"
|
|
>
|
|
<Icon :name="collapsed ? 'lucide:chevron-right' : 'lucide:chevron-left'" size="16" />
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Navigation -->
|
|
<nav class="flex-1 overflow-y-auto py-2">
|
|
<!-- Collapsed view: show only menu item icons -->
|
|
<template v-if="collapsed">
|
|
<ul class="menu menu-vertical menu-compact rounded-none w-full px-2 py-1">
|
|
<!-- Exchange items -->
|
|
<li>
|
|
<NuxtLink
|
|
:to="localePath('/')"
|
|
:class="{ active: isExactActive('/') }"
|
|
class="tooltip tooltip-right"
|
|
:data-tip="t('nav.search')"
|
|
>
|
|
<Icon name="lucide:search" size="18" />
|
|
</NuxtLink>
|
|
</li>
|
|
<li>
|
|
<NuxtLink
|
|
:to="localePath('/catalog/offers')"
|
|
:class="{ active: isActive('/catalog/offers') }"
|
|
class="tooltip tooltip-right"
|
|
:data-tip="t('nav.offers')"
|
|
>
|
|
<Icon name="lucide:tag" size="18" />
|
|
</NuxtLink>
|
|
</li>
|
|
<li>
|
|
<NuxtLink
|
|
:to="localePath('/catalog/suppliers')"
|
|
:class="{ active: isActive('/catalog/suppliers') }"
|
|
class="tooltip tooltip-right"
|
|
:data-tip="t('nav.suppliers')"
|
|
>
|
|
<Icon name="lucide:building-2" size="18" />
|
|
</NuxtLink>
|
|
</li>
|
|
<li>
|
|
<NuxtLink
|
|
:to="localePath('/catalog/hubs')"
|
|
:class="{ active: isActive('/catalog/hubs') }"
|
|
class="tooltip tooltip-right"
|
|
:data-tip="t('nav.hubs')"
|
|
>
|
|
<Icon name="lucide:warehouse" size="18" />
|
|
</NuxtLink>
|
|
</li>
|
|
|
|
<!-- Logged in items -->
|
|
<template v-if="loggedIn">
|
|
<li class="my-2"><div class="divider my-0 h-px"></div></li>
|
|
<li>
|
|
<NuxtLink
|
|
:to="localePath('/clientarea/orders')"
|
|
:class="{ active: isActive('/clientarea/orders') }"
|
|
class="tooltip tooltip-right"
|
|
:data-tip="t('cabinetNav.orders')"
|
|
>
|
|
<Icon name="lucide:package" size="18" />
|
|
</NuxtLink>
|
|
</li>
|
|
<li>
|
|
<NuxtLink
|
|
:to="localePath('/clientarea/addresses')"
|
|
:class="{ active: isActive('/clientarea/addresses') }"
|
|
class="tooltip tooltip-right"
|
|
:data-tip="t('cabinetNav.addresses')"
|
|
>
|
|
<Icon name="lucide:map-pin" size="18" />
|
|
</NuxtLink>
|
|
</li>
|
|
<li>
|
|
<NuxtLink
|
|
:to="localePath('/clientarea/billing')"
|
|
:class="{ active: isActive('/clientarea/billing') }"
|
|
class="tooltip tooltip-right"
|
|
:data-tip="t('cabinetNav.billing')"
|
|
>
|
|
<Icon name="lucide:wallet" size="18" />
|
|
</NuxtLink>
|
|
</li>
|
|
|
|
<template v-if="isSeller">
|
|
<li>
|
|
<NuxtLink
|
|
:to="localePath('/clientarea/offers')"
|
|
:class="{ active: isActive('/clientarea/offers') }"
|
|
class="tooltip tooltip-right"
|
|
:data-tip="t('cabinetNav.offers')"
|
|
>
|
|
<Icon name="lucide:tag" size="18" />
|
|
</NuxtLink>
|
|
</li>
|
|
</template>
|
|
|
|
<li class="my-2"><div class="divider my-0 h-px"></div></li>
|
|
<li>
|
|
<NuxtLink
|
|
:to="localePath('/clientarea/ai')"
|
|
:class="{ active: isActive('/clientarea/ai') }"
|
|
class="tooltip tooltip-right"
|
|
:data-tip="t('cabinetNav.ai')"
|
|
>
|
|
<Icon name="lucide:sparkles" size="18" />
|
|
</NuxtLink>
|
|
</li>
|
|
|
|
<li class="my-2"><div class="divider my-0 h-px"></div></li>
|
|
<li>
|
|
<NuxtLink
|
|
:to="localePath('/clientarea/profile')"
|
|
:class="{ active: isActive('/clientarea/profile') }"
|
|
class="tooltip tooltip-right"
|
|
:data-tip="t('cabinetNav.profile')"
|
|
>
|
|
<Icon name="lucide:user" size="18" />
|
|
</NuxtLink>
|
|
</li>
|
|
<li>
|
|
<NuxtLink
|
|
:to="localePath('/clientarea/team')"
|
|
:class="{ active: isActive('/clientarea/team') }"
|
|
class="tooltip tooltip-right"
|
|
:data-tip="t('cabinetNav.team')"
|
|
>
|
|
<Icon name="lucide:users" size="18" />
|
|
</NuxtLink>
|
|
</li>
|
|
</template>
|
|
</ul>
|
|
</template>
|
|
|
|
<!-- Expanded view -->
|
|
<template v-else>
|
|
<ul class="menu rounded-none w-full gap-1 px-2 py-3">
|
|
<!-- Exchange section -->
|
|
<li>
|
|
<details :open="sections.exchange">
|
|
<summary @click.prevent="toggleSection('exchange')" class="flex items-center gap-2 text-xs uppercase tracking-wide font-semibold text-base-content/70">
|
|
<Icon name="lucide:layers" size="16" />
|
|
<span>{{ t('sidebar.exchange') }}</span>
|
|
</summary>
|
|
<ul>
|
|
<li>
|
|
<NuxtLink :to="localePath('/')" :class="{ active: isExactActive('/') }">
|
|
<Icon name="lucide:search" size="18" />
|
|
{{ t('nav.search') }}
|
|
</NuxtLink>
|
|
</li>
|
|
<li>
|
|
<NuxtLink :to="localePath('/catalog/offers')" :class="{ active: isActive('/catalog/offers') }">
|
|
<Icon name="lucide:tag" size="18" />
|
|
{{ t('nav.offers') }}
|
|
</NuxtLink>
|
|
</li>
|
|
<li>
|
|
<NuxtLink :to="localePath('/catalog/suppliers')" :class="{ active: isActive('/catalog/suppliers') }">
|
|
<Icon name="lucide:building-2" size="18" />
|
|
{{ t('nav.suppliers') }}
|
|
</NuxtLink>
|
|
</li>
|
|
<li>
|
|
<NuxtLink :to="localePath('/catalog/hubs')" :class="{ active: isActive('/catalog/hubs') }">
|
|
<Icon name="lucide:warehouse" size="18" />
|
|
{{ t('nav.hubs') }}
|
|
</NuxtLink>
|
|
</li>
|
|
</ul>
|
|
</details>
|
|
</li>
|
|
|
|
<!-- Cabinet section - only for logged in users -->
|
|
<template v-if="loggedIn">
|
|
<!-- Orders & Logistics -->
|
|
<li>
|
|
<details :open="sections.orders">
|
|
<summary @click.prevent="toggleSection('orders')" class="flex items-center gap-2 text-xs uppercase tracking-wide font-semibold text-base-content/70">
|
|
<Icon name="lucide:truck" size="16" />
|
|
<span>{{ t('sidebar.ordersLogistics') }}</span>
|
|
</summary>
|
|
<ul>
|
|
<li>
|
|
<NuxtLink :to="localePath('/clientarea/orders')" :class="{ active: isActive('/clientarea/orders') }">
|
|
<Icon name="lucide:package" size="18" />
|
|
{{ t('cabinetNav.orders') }}
|
|
</NuxtLink>
|
|
</li>
|
|
<li>
|
|
<NuxtLink :to="localePath('/clientarea/addresses')" :class="{ active: isActive('/clientarea/addresses') }">
|
|
<Icon name="lucide:map-pin" size="18" />
|
|
{{ t('cabinetNav.addresses') }}
|
|
</NuxtLink>
|
|
</li>
|
|
<li>
|
|
<NuxtLink :to="localePath('/clientarea/billing')" :class="{ active: isActive('/clientarea/billing') }">
|
|
<Icon name="lucide:wallet" size="18" />
|
|
{{ t('cabinetNav.billing') }}
|
|
</NuxtLink>
|
|
</li>
|
|
</ul>
|
|
</details>
|
|
</li>
|
|
|
|
<!-- Seller (only for sellers) -->
|
|
<li v-if="isSeller">
|
|
<details :open="sections.seller">
|
|
<summary @click.prevent="toggleSection('seller')" class="flex items-center gap-2 text-xs uppercase tracking-wide font-semibold text-base-content/70">
|
|
<Icon name="lucide:badge-dollar-sign" size="16" />
|
|
<span>{{ t('sidebar.seller') }}</span>
|
|
</summary>
|
|
<ul>
|
|
<li>
|
|
<NuxtLink :to="localePath('/clientarea/offers')" :class="{ active: isActive('/clientarea/offers') }">
|
|
<Icon name="lucide:tag" size="18" />
|
|
{{ t('cabinetNav.offers') }}
|
|
</NuxtLink>
|
|
</li>
|
|
</ul>
|
|
</details>
|
|
</li>
|
|
|
|
<!-- AI & Tools -->
|
|
<li>
|
|
<details :open="sections.ai">
|
|
<summary @click.prevent="toggleSection('ai')" class="flex items-center gap-2 text-xs uppercase tracking-wide font-semibold text-base-content/70">
|
|
<Icon name="lucide:sparkles" size="16" />
|
|
<span>{{ t('sidebar.aiTools') }}</span>
|
|
</summary>
|
|
<ul>
|
|
<li>
|
|
<NuxtLink :to="localePath('/clientarea/ai')" :class="{ active: isActive('/clientarea/ai') }">
|
|
<Icon name="lucide:sparkles" size="18" />
|
|
{{ t('cabinetNav.ai') }}
|
|
</NuxtLink>
|
|
</li>
|
|
</ul>
|
|
</details>
|
|
</li>
|
|
|
|
<!-- Settings -->
|
|
<li>
|
|
<details :open="sections.settings">
|
|
<summary @click.prevent="toggleSection('settings')" class="flex items-center gap-2 text-xs uppercase tracking-wide font-semibold text-base-content/70">
|
|
<Icon name="lucide:settings" size="16" />
|
|
<span>{{ t('sidebar.settings') }}</span>
|
|
</summary>
|
|
<ul>
|
|
<li>
|
|
<NuxtLink :to="localePath('/clientarea/profile')" :class="{ active: isActive('/clientarea/profile') }">
|
|
<Icon name="lucide:user" size="18" />
|
|
{{ t('cabinetNav.profile') }}
|
|
</NuxtLink>
|
|
</li>
|
|
<li>
|
|
<NuxtLink :to="localePath('/clientarea/team')" :class="{ active: isActive('/clientarea/team') }">
|
|
<Icon name="lucide:users" size="18" />
|
|
{{ t('cabinetNav.team') }}
|
|
</NuxtLink>
|
|
</li>
|
|
</ul>
|
|
</details>
|
|
</li>
|
|
</template>
|
|
</ul>
|
|
</template>
|
|
</nav>
|
|
|
|
<!-- Company switcher (bottom) - only for logged in users -->
|
|
<div v-if="!collapsed" class="p-3 border-t border-base-300">
|
|
<template v-if="userData?.teams?.length">
|
|
<div class="dropdown dropdown-top w-full">
|
|
<div
|
|
tabindex="0"
|
|
role="button"
|
|
class="btn btn-ghost btn-sm w-full justify-start gap-2"
|
|
>
|
|
<div class="avatar placeholder">
|
|
<div class="bg-primary text-primary-content rounded w-8 h-8 flex items-center justify-center">
|
|
<span class="text-xs">{{ getTeamInitials(userData.activeTeam?.name) }}</span>
|
|
</div>
|
|
</div>
|
|
<div class="flex-1 text-left truncate">
|
|
<div class="text-sm font-medium truncate">{{ userData.activeTeam?.name || 'Select company' }}</div>
|
|
</div>
|
|
<Icon name="lucide:chevrons-up-down" size="16" class="opacity-50" />
|
|
</div>
|
|
<ul
|
|
tabindex="0"
|
|
class="dropdown-content menu bg-base-100 rounded-box z-50 w-full p-2 shadow-lg border border-base-300 mb-2"
|
|
>
|
|
<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:plus" size="16" />
|
|
{{ t('sidebar.createCompany') }}
|
|
</NuxtLink>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</template>
|
|
<template v-else-if="loggedIn">
|
|
<NuxtLink
|
|
:to="localePath('/clientarea/team')"
|
|
class="btn btn-ghost btn-sm w-full justify-start gap-2"
|
|
>
|
|
<Icon name="lucide:building-2" size="18" />
|
|
<span>{{ t('sidebar.joinCompany') }}</span>
|
|
</NuxtLink>
|
|
</template>
|
|
</div>
|
|
</aside>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
defineEmits(['switch-team'])
|
|
|
|
defineProps<{
|
|
userData?: {
|
|
activeTeam?: { name?: string }
|
|
activeTeamId?: string
|
|
teams?: Array<{ id?: string; name?: string }>
|
|
} | null
|
|
isSeller?: boolean
|
|
loggedIn?: boolean
|
|
}>()
|
|
|
|
const localePath = useLocalePath()
|
|
const route = useRoute()
|
|
const { t } = useI18n()
|
|
|
|
// Sidebar collapse state
|
|
const collapsed = ref(false)
|
|
|
|
// Section open states
|
|
const sections = reactive({
|
|
exchange: true,
|
|
orders: true,
|
|
seller: true,
|
|
ai: true,
|
|
settings: true
|
|
})
|
|
|
|
const toggleSection = (section: keyof typeof sections) => {
|
|
sections[section] = !sections[section]
|
|
}
|
|
|
|
const isActive = (path: string) => {
|
|
const current = route.path
|
|
if (current === localePath(path)) return true
|
|
return current.startsWith(localePath(path) + '/')
|
|
}
|
|
|
|
const isExactActive = (path: string) => {
|
|
return route.path === localePath(path)
|
|
}
|
|
|
|
const getTeamInitials = (name?: string) => {
|
|
if (!name) return '?'
|
|
return name
|
|
.split(' ')
|
|
.map(w => w[0])
|
|
.join('')
|
|
.slice(0, 2)
|
|
.toUpperCase()
|
|
}
|
|
</script>
|