diff --git a/apps/web/src/components/UiIcon.vue b/apps/web/src/components/UiIcon.vue
index 787b08c..efc4b8e 100644
--- a/apps/web/src/components/UiIcon.vue
+++ b/apps/web/src/components/UiIcon.vue
@@ -57,7 +57,8 @@ defineProps<{
| 'apps-plus'
| 'crown'
| 'gift'
- | 'medal';
+ | 'medal'
+ | 'logout';
size?: number;
}>();
@@ -340,6 +341,11 @@ defineProps<{
+
+
+
+
+
diff --git a/apps/web/src/layouts/AppLayout.vue b/apps/web/src/layouts/AppLayout.vue
index 83653f7..f60a457 100644
--- a/apps/web/src/layouts/AppLayout.vue
+++ b/apps/web/src/layouts/AppLayout.vue
@@ -14,7 +14,7 @@ import type {
NotificationsListDto,
} from '@mento/shared';
import { brand } from '@mento/shared';
-import { USER_ROLE_LABELS, canManageSettings, canViewAllRecords } from '@/lib/roles';
+import { canManageSettings, canViewAllRecords } from '@/lib/roles';
const auth = useAuthStore();
const router = useRouter();
@@ -24,11 +24,12 @@ const canSettings = computed(() => canManageSettings(auth.user?.role));
const showOwnRecordsBanner = computed(
() => Boolean(auth.user?.role) && !canViewAllRecords(auth.user?.role),
);
-const roleLabel = computed(() =>
- auth.user?.role ? USER_ROLE_LABELS[auth.user.role] : '',
-);
const todayLabel = computed(() => formatJalaliShort(new Date()));
+const SIDEBAR_KEY = 'mento_sidebar_collapsed';
+const collapsed = ref(false);
+const moreFlyout = ref(false);
+
const salesOpen = ref(false);
const moreOpen = ref(false);
const settingsOpen = ref(false);
@@ -58,6 +59,21 @@ const salesActive = computed(() => pathIn(salesPaths));
const moreActive = computed(() => pathIn(morePaths));
const settingsActive = computed(() => pathIn(settingsPaths));
const productsActive = computed(() => route.path.startsWith('/settings/products'));
+const extraActive = computed(() =>
+ pathIn([
+ '/quotes',
+ '/invoices',
+ '/sales/forecast',
+ '/notes',
+ '/tickets',
+ '/settings/automations',
+ '/settings/lead-forms',
+ '/settings/custom-fields',
+ '/settings/currencies',
+ '/settings/tahesab',
+ '/settings/faq',
+ ]) || moreActive.value,
+);
watch(
() => route.path,
@@ -88,6 +104,32 @@ const hasUnread = computed(() => unreadCount.value > 0);
function closeMenu() {
menuOpen.value = false;
+ moreFlyout.value = false;
+}
+
+function toggleCollapsed() {
+ collapsed.value = !collapsed.value;
+ moreFlyout.value = false;
+ try {
+ localStorage.setItem(SIDEBAR_KEY, collapsed.value ? '1' : '0');
+ } catch {
+ // ignore
+ }
+}
+
+const moreBtnRef = ref(null);
+const flyoutRef = ref(null);
+
+function toggleMoreFlyout() {
+ moreFlyout.value = !moreFlyout.value;
+}
+
+function onDocClick(event: MouseEvent) {
+ if (!moreFlyout.value) return;
+ const target = event.target;
+ if (!(target instanceof Node)) return;
+ if (moreBtnRef.value?.contains(target) || flyoutRef.value?.contains(target)) return;
+ moreFlyout.value = false;
}
function closeNotif() {
@@ -189,24 +231,54 @@ watch(
);
onMounted(() => {
+ try {
+ collapsed.value = localStorage.getItem(SIDEBAR_KEY) === '1';
+ } catch {
+ collapsed.value = false;
+ }
void refreshNotifs();
void loadActivityBadge();
pollTimer = setInterval(() => void refreshNotifs(), 45000);
+ document.addEventListener('click', onDocClick, true);
});
onUnmounted(() => {
if (pollTimer) clearInterval(pollTimer);
+ document.removeEventListener('click', onDocClick, true);
document.body.style.overflow = '';
});
-