Initial commit from monorepo
This commit is contained in:
395
app/components/Sidebar.vue
Normal file
395
app/components/Sidebar.vue
Normal file
@@ -0,0 +1,395 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user