Collapse sidebar into primary links and expandable groups.

Keep daily work visible and tuck sales, extras, and settings behind toggles.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-30 10:06:41 +03:30
co-authored by Cursor
parent 3b50f14817
commit a30e625349
+229 -25
View File
@@ -18,6 +18,63 @@ const roleLabel = computed(() =>
auth.user?.role ? USER_ROLE_LABELS[auth.user.role] : '',
);
const salesOpen = ref(false);
const moreOpen = ref(false);
const settingsOpen = ref(false);
const salesPaths = [
'/quotes',
'/invoices',
'/sales/forecast',
'/reports',
];
const morePaths = [
'/activities/calendar',
'/projects',
'/project-cards',
'/loyalty',
];
const settingsPaths = [
'/settings/team',
'/settings/automations',
'/settings/lead-forms',
'/settings/custom-fields',
'/settings/products',
'/settings/currencies',
'/settings/faq',
];
function pathIn(prefixes: string[]) {
return prefixes.some(
(p) => route.path === p || route.path.startsWith(`${p}/`),
);
}
const salesActive = computed(() => pathIn(salesPaths));
const moreActive = computed(() => pathIn(morePaths));
const settingsActive = computed(() => pathIn(settingsPaths));
watch(
() => route.path,
() => {
if (salesActive.value) salesOpen.value = true;
if (moreActive.value) moreOpen.value = true;
if (settingsActive.value) settingsOpen.value = true;
},
{ immediate: true },
);
function toggleGroup(
key: 'sales' | 'more' | 'settings',
event: Event,
) {
event.preventDefault();
event.stopPropagation();
if (key === 'sales') salesOpen.value = !salesOpen.value;
if (key === 'more') moreOpen.value = !moreOpen.value;
if (key === 'settings') settingsOpen.value = !settingsOpen.value;
}
const menuOpen = ref(false);
const notifOpen = ref(false);
const loading = ref(false);
@@ -185,29 +242,89 @@ onUnmounted(() => {
</button>
</div>
<nav @click="closeMenu">
<RouterLink to="/">خانه امروز</RouterLink>
<RouterLink to="/activities">فعالیت‌ها</RouterLink>
<RouterLink to="/activities/calendar">تقویم فعالیت‌ها</RouterLink>
<RouterLink to="/pipeline">کاریز فروش</RouterLink>
<RouterLink to="/projects">کاریز پروژه</RouterLink>
<RouterLink to="/loyalty">باشگاه مشتریان</RouterLink>
<RouterLink to="/sales/forecast">هدف و پیش‌بینی</RouterLink>
<RouterLink to="/reports">گزارشات</RouterLink>
<RouterLink to="/tickets">تیکت‌ها</RouterLink>
<RouterLink to="/quotes">پیش‌فاکتورها</RouterLink>
<RouterLink to="/invoices">فاکتورها</RouterLink>
<RouterLink to="/contacts">اشخاص و شرکت‌ها</RouterLink>
<RouterLink to="/notes">یادداشت‌ها</RouterLink>
<template v-if="canSettings">
<RouterLink to="/settings/team">تیم و نقش‌ها</RouterLink>
<RouterLink to="/settings/automations">اتوماسیون</RouterLink>
<RouterLink to="/settings/lead-forms">فرم‌ساز سرنخ</RouterLink>
<RouterLink to="/settings/custom-fields">فیلدهای سفارشی</RouterLink>
<RouterLink to="/settings/products">محصولات</RouterLink>
<RouterLink to="/settings/currencies">ارزها</RouterLink>
<RouterLink to="/settings/faq">FAQ</RouterLink>
</template>
<nav class="side-nav">
<div class="nav-primary">
<RouterLink to="/" @click="closeMenu">خانه امروز</RouterLink>
<RouterLink
to="/activities"
active-class=""
exact-active-class=""
:class="{
'router-link-active':
route.path === '/activities' ||
route.path.startsWith('/activities/new') ||
(route.path.startsWith('/activities/') &&
!route.path.startsWith('/activities/calendar')),
}"
@click="closeMenu"
>
فعالیت‌ها
</RouterLink>
<RouterLink to="/pipeline" @click="closeMenu">کاریز فروش</RouterLink>
<RouterLink to="/contacts" @click="closeMenu">اشخاص و شرکت‌ها</RouterLink>
<RouterLink to="/tickets" @click="closeMenu">تیکت‌ها</RouterLink>
<RouterLink to="/notes" @click="closeMenu">یادداشت‌ها</RouterLink>
</div>
<div class="nav-group" :class="{ open: salesOpen, active: salesActive }">
<button
class="nav-group-btn"
type="button"
:aria-expanded="salesOpen"
@click="toggleGroup('sales', $event)"
>
<span>فروش و مالی</span>
<span class="chev" aria-hidden="true" />
</button>
<div v-show="salesOpen" class="nav-sub">
<RouterLink to="/quotes" @click="closeMenu">پیش‌فاکتورها</RouterLink>
<RouterLink to="/invoices" @click="closeMenu">فاکتورها</RouterLink>
<RouterLink to="/sales/forecast" @click="closeMenu">هدف و پیش‌بینی</RouterLink>
<RouterLink to="/reports" @click="closeMenu">گزارشات</RouterLink>
</div>
</div>
<div class="nav-group" :class="{ open: moreOpen, active: moreActive }">
<button
class="nav-group-btn"
type="button"
:aria-expanded="moreOpen"
@click="toggleGroup('more', $event)"
>
<span>موارد بیشتر</span>
<span class="chev" aria-hidden="true" />
</button>
<div v-show="moreOpen" class="nav-sub">
<RouterLink to="/activities/calendar" @click="closeMenu">تقویم فعالیت‌ها</RouterLink>
<RouterLink to="/projects" @click="closeMenu">کاریز پروژه</RouterLink>
<RouterLink to="/loyalty" @click="closeMenu">باشگاه مشتریان</RouterLink>
</div>
</div>
<div
v-if="canSettings"
class="nav-group"
:class="{ open: settingsOpen, active: settingsActive }"
>
<button
class="nav-group-btn"
type="button"
:aria-expanded="settingsOpen"
@click="toggleGroup('settings', $event)"
>
<span>تنظیمات</span>
<span class="chev" aria-hidden="true" />
</button>
<div v-show="settingsOpen" class="nav-sub">
<RouterLink to="/settings/team" @click="closeMenu">تیم و نقش‌ها</RouterLink>
<RouterLink to="/settings/automations" @click="closeMenu">اتوماسیون</RouterLink>
<RouterLink to="/settings/lead-forms" @click="closeMenu">فرم‌ساز سرنخ</RouterLink>
<RouterLink to="/settings/custom-fields" @click="closeMenu">فیلدهای سفارشی</RouterLink>
<RouterLink to="/settings/products" @click="closeMenu">محصولات</RouterLink>
<RouterLink to="/settings/currencies" @click="closeMenu">ارزها</RouterLink>
<RouterLink to="/settings/faq" @click="closeMenu">FAQ</RouterLink>
</div>
</div>
</nav>
<p v-if="showOwnRecordsBanner" class="scope-banner">
@@ -296,9 +413,12 @@ onUnmounted(() => {
padding: 1.4rem;
display: flex;
flex-direction: column;
gap: 1.5rem;
gap: 1.1rem;
min-height: 100vh;
min-height: 100dvh;
max-height: 100vh;
max-height: 100dvh;
overflow: hidden;
}
.brand {
@@ -323,9 +443,93 @@ onUnmounted(() => {
color: #9fb3aa;
}
nav {
nav,
.side-nav {
display: grid;
gap: 0.35rem;
overflow-y: auto;
min-height: 0;
flex: 1;
padding-left: 0.15rem;
margin-left: -0.15rem;
}
.nav-primary,
.nav-sub {
display: grid;
gap: 0.28rem;
}
.side-nav a {
padding: 0.65rem 0.85rem;
border-radius: 10px;
color: #d7e4de;
text-decoration: none;
}
.side-nav a.router-link-active {
background: rgba(255, 255, 255, 0.08);
color: #fff;
}
.nav-group {
display: grid;
gap: 0.2rem;
margin-top: 0.25rem;
}
.nav-group-btn {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.5rem;
border: 0;
background: transparent;
color: #9fb3aa;
font: inherit;
font-size: 0.82rem;
font-weight: 600;
letter-spacing: 0.01em;
padding: 0.55rem 0.85rem;
border-radius: 10px;
cursor: pointer;
text-align: right;
}
.nav-group-btn:hover {
background: rgba(255, 255, 255, 0.05);
color: #d7e4de;
}
.nav-group.active > .nav-group-btn {
color: #c5ddd3;
}
.chev {
width: 0.45rem;
height: 0.45rem;
border-right: 1.5px solid currentColor;
border-bottom: 1.5px solid currentColor;
transform: rotate(45deg);
transition: transform 0.15s ease;
flex-shrink: 0;
opacity: 0.85;
}
.nav-group.open .chev {
transform: rotate(-135deg);
}
.nav-sub {
padding: 0.1rem 0 0.25rem 0.35rem;
border-right: 1px solid rgba(255, 255, 255, 0.08);
margin-right: 0.85rem;
}
.nav-sub a {
padding: 0.55rem 0.75rem;
font-size: 0.92rem;
}
nav a {