Bladeren bron

✨ feat(layout): implementar menus laterais por tipo de usuário e header adaptativo

Fase: dev | Origin: melhoria-interna
Gustavo Zanatta 1 week geleden
bovenliggende
commit
e76be6609c

+ 98 - 0
src/components/layout/AppHeader.vue

@@ -0,0 +1,98 @@
+<template>
+  <q-toolbar class="app-header">
+    <div class="app-header__left row items-center no-wrap">
+      <q-img :src="LogoSmall" fit="contain" class="app-header__logo" />
+      <q-btn
+        flat
+        round
+        dense
+        class="app-header__toggle q-ml-xs"
+        :icon="navStore.miniState ? 'mdi-chevron-right' : 'mdi-chevron-left'"
+        @click="navStore.toggleMini()"
+      />
+    </div>
+
+    <div class="app-header__type-label text-white text-weight-medium">
+      {{ userTypeLabel }}
+    </div>
+
+    <q-space />
+
+    <q-avatar size="36px" class="app-header__avatar">
+      <img v-if="store.user?.photo_url" :src="store.user.photo_url" />
+      <span v-else class="app-header__avatar-initial">{{ userInitial }}</span>
+    </q-avatar>
+  </q-toolbar>
+</template>
+
+<script setup>
+import { computed } from "vue";
+import { userStore } from "src/stores/user";
+import { navigationStore } from "src/stores/navigation";
+import LogoSmall from "src/assets/logo_serprati_small.png";
+
+const store = userStore();
+const navStore = navigationStore();
+
+const userTypeLabel = computed(() => {
+  const t = store.userTipo;
+  if (t === "administrador") return "Administração";
+  if (t === "associado") return "Associado";
+  if (t === "parceiro") return "Parceiro";
+  return "";
+});
+
+const userInitial = computed(() => {
+  const name = store.user?.name || store.user?.email || "";
+  return name.charAt(0).toUpperCase();
+});
+</script>
+
+<style lang="scss" scoped>
+@use "src/css/quasar.variables.scss" as vars;
+
+.app-header {
+  height: 52px;
+  min-height: 52px;
+  background: linear-gradient(90deg, vars.$violet-darker 0%, #0f0215 100%);
+  padding: 0 16px;
+}
+
+.app-header__left {
+  flex-shrink: 0;
+}
+
+.app-header__logo {
+  width: 36px;
+  height: 36px;
+}
+
+.app-header__toggle {
+  color: rgba(white, 0.75) !important;
+
+  &:hover {
+    color: white !important;
+    background: rgba(white, 0.1) !important;
+  }
+}
+
+.app-header__type-label {
+  font-size: 14px;
+  letter-spacing: 0.3px;
+  opacity: 0.9;
+  margin-left: 16px;
+}
+
+.app-header__avatar {
+  background: rgba(white, 0.15);
+  border: 2px solid rgba(white, 0.3);
+  cursor: pointer;
+  flex-shrink: 0;
+}
+
+.app-header__avatar-initial {
+  color: white;
+  font-size: 15px;
+  font-weight: 600;
+}
+</style>

+ 83 - 67
src/components/layout/DefaultHeaderPage.vue

@@ -1,8 +1,9 @@
 <template>
   <div>
-    <q-breadcrumbs
+    <!-- <q-breadcrumbs
       v-if="displayBreadcrumbs != null"
       class="q-mb-xs"
+      active-color="violet-normal"
       :class="$q.screen.lt.sm ? '' : 'q-pl-lg'"
     >
       <q-breadcrumbs-el
@@ -11,28 +12,40 @@
         :label="crumb.title"
         :to="crumb.name ? { name: crumb.name, params: crumb.params } : null"
       />
-    </q-breadcrumbs>
-    <div
+    </q-breadcrumbs> -->
+    <!-- <div
       v-else
       style="max-width: 180px"
       :class="$q.screen.lt.sm ? '' : 'q-pl-lg'"
     >
       <q-skeleton type="text" />
-    </div>
+    </div> -->
 
     <div class="flex items-center justify-between">
-      <div class="column q-pl-xs" :class="$q.screen.lt.sm ? '' : 'q-pt-md'">
-        <span v-if="displayTitle" class="text-h4 text-primary q-mb-xs">
-          {{ displayTitle }}
-        </span>
-        <div v-else style="width: 280px">
-          <q-skeleton type="text" height="40px" />
-        </div>
-        <span v-if="displayDescription" class="text-body2">
-          {{ displayDescription }}
-        </span>
-        <div v-else style="width: 380px">
-          <q-skeleton type="text" height="20px" />
+      <div class="row items-center no-wrap" :class="$q.screen.lt.sm ? '' : 'q-pt-md'">
+        <q-btn
+          v-if="showBack"
+          flat
+          round
+          dense
+          icon="mdi-arrow-left"
+          color="violet-normal"
+          class="q-mr-xs"
+          @click="router.back()"
+        />
+        <div class="column q-pl-xs">
+          <span v-if="displayTitle" class="text-h4 text-violet-normal q-mb-xs">
+            {{ displayTitle }}
+          </span>
+          <div v-else style="width: 280px">
+            <q-skeleton type="text" height="40px" />
+          </div>
+          <!-- <span v-if="displayDescription != null" class="text-body2">
+            {{ displayDescription }}
+          </span> -->
+          <!-- <div v-else style="width: 380px">
+            <q-skeleton type="text" height="20px" />
+          </div> -->
         </div>
       </div>
       <div>
@@ -45,10 +58,10 @@
 
 <script setup>
 import { computed } from "vue";
-import { useRoute } from "vue-router";
+import { useRoute, useRouter } from "vue-router";
 import { useI18n } from "vue-i18n";
 
-const { title, description, breadcrumbs } = defineProps({
+const { title } = defineProps({
   title: {
     type: Object,
     default: null,
@@ -63,8 +76,11 @@ const { title, description, breadcrumbs } = defineProps({
   },
 });
 
-const route = useRoute();
-const { t } = useI18n();
+const route  = useRoute();
+const router = useRouter();
+const { t }  = useI18n();
+
+const showBack = computed(() => (route.meta?.breadcrumbs?.length ?? 0) > 1);
 
 const displayTitle = computed(() => {
   if (title) {
@@ -83,52 +99,52 @@ const displayTitle = computed(() => {
   return null;
 });
 
-const displayDescription = computed(() => {
-  if (description) {
-    if (description.translate) {
-      return t(description.value);
-    } else {
-      return description.value;
-    }
-  } else if (route.meta?.description) {
-    if (route.meta?.description.translate) {
-      return t(route.meta?.description.value);
-    } else {
-      return route.meta?.description.value;
-    }
-  }
-  return null;
-});
+// const displayDescription = computed(() => {
+//   if (description) {
+//     if (description.translate) {
+//       return t(description.value);
+//     } else {
+//       return description.value;
+//     }
+//   } else if (route.meta?.description) {
+//     if (route.meta?.description.translate) {
+//       return t(route.meta?.description.value);
+//     } else {
+//       return route.meta?.description.value;
+//     }
+//   }
+//   return null;
+// });
 
-const displayBreadcrumbs = computed(() => {
-  if (!breadcrumbs && breadcrumbs?.length <= 0) {
-    return null;
-  } else if (breadcrumbs && breadcrumbs?.length > 0) {
-    return (breadcrumbs || []).map((b) => {
-      if (b.translate) {
-        return t(b.title);
-      } else {
-        return b.title;
-      }
-    });
-  }
-  if (!route.meta?.breadcrumbs && route.meta?.breadcrumbs?.length <= 0) {
-    return null;
-  } else if (route.meta?.breadcrumbs && route.meta?.breadcrumbs?.length > 0) {
-    return (route.meta?.breadcrumbs || []).map((b) => {
-      if (b.translate) {
-        return {
-          ...b,
-          title: t(b.title),
-        };
-      } else {
-        return {
-          ...b,
-          title: b.title,
-        };
-      }
-    });
-  }
-  return null;
-});
+// const displayBreadcrumbs = computed(() => {
+//   if (!breadcrumbs && breadcrumbs?.length <= 0) {
+//     return null;
+//   } else if (breadcrumbs && breadcrumbs?.length > 0) {
+//     return (breadcrumbs || []).map((b) => {
+//       if (b.translate) {
+//         return t(b.title);
+//       } else {
+//         return b.title;
+//       }
+//     });
+//   }
+//   if (!route.meta?.breadcrumbs && route.meta?.breadcrumbs?.length <= 0) {
+//     return null;
+//   } else if (route.meta?.breadcrumbs && route.meta?.breadcrumbs?.length > 0) {
+//     return (route.meta?.breadcrumbs || []).map((b) => {
+//       if (b.translate) {
+//         return {
+//           ...b,
+//           title: t(b.title),
+//         };
+//       } else {
+//         return {
+//           ...b,
+//           title: b.title,
+//         };
+//       }
+//     });
+//   }
+//   return null;
+// });
 </script>

+ 14 - 89
src/components/layout/LeftMenuLayoutAdministrador.vue

@@ -8,41 +8,11 @@
     :width="235"
     :mini-width="64"
     :breakpoint="500"
-    :mini="miniState"
+    :mini="navigation_store.miniState"
     :behavior="'desktop'"
     class="left-menu-drawer"
   >
     <div class="column full-height no-wrap left-menu-inner">
-      <div class="left-menu-logo-wrapper" :class="{ 'left-menu-logo-wrapper--mini': miniState }">
-        <q-img
-          v-if="!miniState"
-          :src="Logo"
-          fit="contain"
-          class="left-menu-logo"
-        />
-        <q-img
-          v-else
-          :src="LogoSmall"
-          fit="contain"
-          class="left-menu-logo--mini"
-        />
-      </div>
-      <div
-        v-if="!$q.screen.lt.md"
-        class="toggle-button-wrapper absolute"
-        style="top: 2px; right: -32px; z-index: 1"
-      >
-        <div @click="miniState = !miniState">
-          <q-icon
-            size="sm"
-            :name="miniState ? 'mdi-page-layout-sidebar-right' : 'mdi-page-layout-sidebar-left'"
-            color="violet-normal"
-          />
-          <q-tooltip anchor="center right" self="center left" :offset="[10, 10]">
-            {{ miniState ? $t('ui.navigation.expand_menu') : $t('ui.navigation.collapse_menu') }}
-          </q-tooltip>
-        </div>
-      </div>
       <div class="left-menu-items-wrapper column full-height no-wrap">
         <div class="left-menu-bg-overlay" />
         <div class="left-menu-bg-tint" />
@@ -63,16 +33,16 @@
                 <q-item-section avatar>
                   <q-icon :name="item.icon" class="left-menu-icon" />
                 </q-item-section>
-                <q-item-section v-if="!miniState">{{ $t(item.title) }}</q-item-section>
+                <q-item-section v-if="!navigation_store.miniState">{{ $t(item.title) }}</q-item-section>
                 <q-tooltip
-                  v-if="miniState"
+                  v-if="navigation_store.miniState"
                   anchor="center right"
                   self="center left"
                   :offset="[10, 10]"
                 >{{ $t(item.title) }}</q-tooltip>
               </q-item>
               <div v-else>
-                <template v-if="!miniState">
+                <template v-if="!navigation_store.miniState">
                   <q-expansion-item
                     v-model="isExpasionItemExpanded[index]"
                     :class="{ 'menu-selected': childrenAreActive(item.childrens) && !isExpasionItemExpanded[index] }"
@@ -145,8 +115,8 @@
             <q-item-section avatar >
               <q-icon name="logout" class="left-menu-icon left-menu-logout__icon"/>
             </q-item-section>
-            <q-item-section v-if="!miniState" class="text-violet-normal">{{ $t('auth.logout') }}</q-item-section>
-            <q-tooltip v-if="miniState" anchor="center right" self="center left" :offset="[10, 10]">
+            <q-item-section v-if="!navigation_store.miniState" class="text-violet-normal">{{ $t('auth.logout') }}</q-item-section>
+            <q-tooltip v-if="navigation_store.miniState" anchor="center right" self="center left" :offset="[10, 10]">
               {{ $t('auth.logout') }}
             </q-tooltip>
           </q-item>
@@ -154,9 +124,9 @@
           <q-item v-ripple class="left-menu-item" clickable @click="openUrl('https://softpar.inf.br')">
             <div class="flex full-width justify-center">
               <q-img
-                :src="miniState || $q.screen.lt.md ? LogoSoftparMini : $q.dark.isActive ? LogoSoftparLight : LogoSoftparDark"
+                :src="navigation_store.miniState || $q.screen.lt.md ? LogoSoftparMini : $q.dark.isActive ? LogoSoftparLight : LogoSoftparDark"
                 style="height: 30px"
-                :style="miniState || $q.screen.lt.md ? 'max-width: 48px; width: 48px' : 'max-width: 100px; width: 100%'"
+                :style="navigation_store.miniState || $q.screen.lt.md ? 'max-width: 48px; width: 48px' : 'max-width: 100px; width: 100%'"
               />
             </div>
           </q-item>
@@ -166,7 +136,7 @@
           class="full-width text-center q-pb-xs cursor-pointer left-menu-version"
           @click="gotoVersionPage()"
         >
-          <span v-if="!(miniState || $q.screen.lt.md)" class="text-caption text-weight-light">
+          <span v-if="!(navigation_store.miniState || $q.screen.lt.md)" class="text-caption text-weight-light">
             {{ $t('common.terms.version') + ' ' + version }}
           </span>
           <span v-else class="text-caption text-weight-light">{{ version }}</span>
@@ -176,15 +146,13 @@
   </q-drawer>
 </template>
 <script setup>
-import { ref, watch, watchEffect, onMounted } from "vue";
+import { ref, watchEffect, onMounted } from "vue";
 import { useAuth } from "src/composables/useAuth";
 import { useRouter, useRoute } from "vue-router";
 import { navigationStore } from "src/stores/navigation";
-import { useQuasar, Cookies } from "quasar";
+import { useQuasar } from "quasar";
 import { version } from "src/../package.json";
 
-import Logo from "src/assets/logo_serprati.svg";
-import LogoSmall from "src/assets/logo_serprati_small.png";
 import LogoSoftparLight from "src/assets/softpar_logo_light.svg";
 import LogoSoftparDark from "src/assets/softpar_logo_dark.svg";
 import LogoSoftparMini from "src/assets/softpar_logo_mini.svg";
@@ -195,13 +163,6 @@ const route = useRoute();
 const navigation_store = navigationStore();
 const $q = useQuasar();
 
-// const systemTheme = window.matchMedia("(prefers-color-scheme: dark)").matches
-//   ? "dark"
-//   : "light";
-// const systemTheme = "light";
-const miniStateCookies = Cookies.get("miniState");
-const miniState = ref(miniStateCookies === "true" ? true : false);
-
 const childrenAreActive = (children) => {
   if (!Array.isArray(children) || children.length === 0) {
     return false;
@@ -213,8 +174,8 @@ const isExpasionItemExpanded = ref([]);
 
 watchEffect(() => {
   if ($q.screen.lt.md) {
-    miniState.value = true;
-    isExpasionItemExpanded.value.forEach((expansion, index) => {
+    navigation_store.miniState = true;
+    isExpasionItemExpanded.value.forEach((_, index) => {
       isExpasionItemExpanded.value[index] = false;
     });
   }
@@ -233,10 +194,6 @@ const gotoVersionPage = () => {
   router.push({ name: "SystemVersionsPage" });
 };
 
-watch(miniState, () => {
-  Cookies.set("miniState", miniState.value, { path: "/", sameSite: "Lax" });
-});
-
 onMounted(() => {
   navigation_store.navigationItems.forEach((item, index) => {
     if (childrenAreActive(item.childrens)) {
@@ -265,36 +222,11 @@ onMounted(() => {
   background: transparent;
 }
 
-.left-menu-logo-wrapper {
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  background-color: vars.$neutral-light;
-  border-radius: 8px 8px 0 0;
-  padding: 10px 16px;
-  min-height: 64px;
-  flex-shrink: 0;
-}
-
-.left-menu-logo-wrapper--mini {
-  padding: 10px 8px;
-}
-
-.left-menu-logo {
-  width: 110px;
-  height: 40px;
-}
-
-.left-menu-logo--mini {
-  width: 36px;
-  height: 36px;
-}
-
 .left-menu-items-wrapper {
   position: relative;
   flex: 1;
   overflow: hidden;
-  border-radius: 0 0 8px 8px;
+  border-radius: 8px;
 
   &::before {
     content: "";
@@ -372,11 +304,4 @@ onMounted(() => {
   padding-bottom: 6px;
 }
 
-.toggle-button-wrapper {
-  transition: transform 0.3s ease;
-}
-
-.toggle-button-wrapper:hover {
-  transform: scale(1.1);
-}
 </style>

+ 14 - 99
src/components/layout/LeftMenuLayoutAssociado.vue

@@ -8,47 +8,11 @@
     :width="235"
     :mini-width="64"
     :breakpoint="500"
-    :mini="miniState"
+    :mini="navigation_store.miniState"
     :behavior="'desktop'"
     class="left-menu-drawer"
   >
     <div class="column full-height no-wrap left-menu-inner">
-
-      <!-- ── Logo ──────────────────────────────────────────────── -->
-      <div class="left-menu-logo-wrapper" :class="{ 'left-menu-logo-wrapper--mini': miniState }">
-        <q-img
-          v-if="!miniState"
-          :src="Logo"
-          fit="contain"
-          class="left-menu-logo"
-        />
-        <q-img
-          v-else
-          :src="LogoSmall"
-          fit="contain"
-          class="left-menu-logo--mini"
-        />
-      </div>
-
-      <!-- ── Toggle expand/collapse ────────────────────────────── -->
-      <div
-        v-if="!$q.screen.lt.md"
-        class="toggle-button-wrapper absolute"
-        style="top: 2px; right: -32px; z-index: 1"
-      >
-        <div @click="miniState = !miniState">
-          <q-icon
-            size="sm"
-            :name="miniState ? 'mdi-page-layout-sidebar-right' : 'mdi-page-layout-sidebar-left'"
-            color="violet-normal"
-          />
-          <q-tooltip anchor="center right" self="center left" :offset="[10, 10]">
-            {{ miniState ? $t('ui.navigation.expand_menu') : $t('ui.navigation.collapse_menu') }}
-          </q-tooltip>
-        </div>
-      </div>
-
-      <!-- ── Menu items (background image section) ─────────────── -->
       <div class="left-menu-items-wrapper column full-height no-wrap">
         <div class="left-menu-bg-overlay" />
         <div class="left-menu-bg-tint" />
@@ -57,7 +21,6 @@
           <template v-for="(item, index) in navigation_store.navigationItems" :key="index">
             <template v-if="item.permission">
 
-              <!-- Single item -->
               <q-item
                 v-if="item.type === 'single'"
                 v-ripple
@@ -71,18 +34,17 @@
                 <q-item-section avatar>
                   <q-icon :name="item.icon" class="left-menu-icon" />
                 </q-item-section>
-                <q-item-section v-if="!miniState">{{ $t(item.title) }}</q-item-section>
+                <q-item-section v-if="!navigation_store.miniState">{{ $t(item.title) }}</q-item-section>
                 <q-tooltip
-                  v-if="miniState"
+                  v-if="navigation_store.miniState"
                   anchor="center right"
                   self="center left"
                   :offset="[10, 10]"
                 >{{ $t(item.title) }}</q-tooltip>
               </q-item>
 
-              <!-- Expansive item -->
               <div v-else>
-                <template v-if="!miniState">
+                <template v-if="!navigation_store.miniState">
                   <q-expansion-item
                     v-model="isExpasionItemExpanded[index]"
                     :class="{ 'menu-selected': childrenAreActive(item.childrens) && !isExpasionItemExpanded[index] }"
@@ -152,14 +114,13 @@
           </template>
         </q-list>
 
-        <!-- ── Footer ──────────────────────────────────────────── -->
         <q-list class="q-mt-auto left-menu-footer">
           <q-item v-ripple class="left-menu-item left-menu-logout text-center" clickable @click="logoutFn">
             <q-item-section avatar >
               <q-icon name="logout" class="left-menu-icon left-menu-logout__icon"/>
             </q-item-section>
-            <q-item-section v-if="!miniState" class="text-violet-normal">{{ $t('auth.logout') }}</q-item-section>
-            <q-tooltip v-if="miniState" anchor="center right" self="center left" :offset="[10, 10]">
+            <q-item-section v-if="!navigation_store.miniState" class="text-violet-normal">{{ $t('auth.logout') }}</q-item-section>
+            <q-tooltip v-if="navigation_store.miniState" anchor="center right" self="center left" :offset="[10, 10]">
               {{ $t('auth.logout') }}
             </q-tooltip>
           </q-item>
@@ -167,9 +128,9 @@
           <q-item v-ripple class="left-menu-item" clickable @click="openUrl('https://softpar.inf.br')">
             <div class="flex full-width justify-center">
               <q-img
-                :src="miniState || $q.screen.lt.md ? LogoSoftparMini : $q.dark.isActive ? LogoSoftparLight : LogoSoftparDark"
+                :src="navigation_store.miniState || $q.screen.lt.md ? LogoSoftparMini : $q.dark.isActive ? LogoSoftparLight : LogoSoftparDark"
                 style="height: 30px"
-                :style="miniState || $q.screen.lt.md ? 'max-width: 48px; width: 48px' : 'max-width: 100px; width: 100%'"
+                :style="navigation_store.miniState || $q.screen.lt.md ? 'max-width: 48px; width: 48px' : 'max-width: 100px; width: 100%'"
               />
             </div>
           </q-item>
@@ -179,7 +140,7 @@
           class="full-width text-center q-pb-xs cursor-pointer left-menu-version"
           @click="gotoVersionPage()"
         >
-          <span v-if="!(miniState || $q.screen.lt.md)" class="text-caption text-weight-light">
+          <span v-if="!(navigation_store.miniState || $q.screen.lt.md)" class="text-caption text-weight-light">
             {{ $t('common.terms.version') + ' ' + version }}
           </span>
           <span v-else class="text-caption text-weight-light">{{ version }}</span>
@@ -190,15 +151,13 @@
   </q-drawer>
 </template>
 <script setup>
-import { ref, watch, watchEffect, onMounted } from "vue";
+import { ref, watchEffect, onMounted } from "vue";
 import { useAuth } from "src/composables/useAuth";
 import { useRouter, useRoute } from "vue-router";
 import { navigationStore } from "src/stores/navigation";
-import { useQuasar, Cookies } from "quasar";
+import { useQuasar } from "quasar";
 import { version } from "src/../package.json";
 
-import Logo from "src/assets/logo_serprati_associado.svg";
-import LogoSmall from "src/assets/logo_serprati_associado_small.svg";
 import LogoSoftparLight from "src/assets/softpar_logo_light.svg";
 import LogoSoftparDark from "src/assets/softpar_logo_dark.svg";
 import LogoSoftparMini from "src/assets/softpar_logo_mini.svg";
@@ -209,13 +168,6 @@ const route = useRoute();
 const navigation_store = navigationStore();
 const $q = useQuasar();
 
-// const systemTheme = window.matchMedia("(prefers-color-scheme: dark)").matches
-//   ? "dark"
-//   : "light";
-// const systemTheme = "light";
-const miniStateCookies = Cookies.get("miniState");
-const miniState = ref(miniStateCookies === "true" ? true : false);
-
 const childrenAreActive = (children) => {
   if (!Array.isArray(children) || children.length === 0) {
     return false;
@@ -227,8 +179,8 @@ const isExpasionItemExpanded = ref([]);
 
 watchEffect(() => {
   if ($q.screen.lt.md) {
-    miniState.value = true;
-    isExpasionItemExpanded.value.forEach((expansion, index) => {
+    navigation_store.miniState = true;
+    isExpasionItemExpanded.value.forEach((_, index) => {
       isExpasionItemExpanded.value[index] = false;
     });
   }
@@ -247,10 +199,6 @@ const gotoVersionPage = () => {
   router.push({ name: "SystemVersionsPage" });
 };
 
-watch(miniState, () => {
-  Cookies.set("miniState", miniState.value, { path: "/", sameSite: "Lax" });
-});
-
 onMounted(() => {
   navigation_store.navigationItems.forEach((item, index) => {
     if (childrenAreActive(item.childrens)) {
@@ -264,7 +212,6 @@ onMounted(() => {
 @use "sass:map";
 @use "src/css/quasar.variables.scss" as vars;
 
-// ── Drawer container ───────────────────────────────────────────────────────
 :deep(.q-drawer) {
   background: transparent !important;
   box-shadow: none !important;
@@ -280,36 +227,11 @@ onMounted(() => {
   background: transparent;
 }
 
-// ── Logo wrapper ───────────────────────────────────────────────────────────
-.left-menu-logo-wrapper {
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  background-color: vars.$violet-normal;
-  border-radius: 8px 8px 0 0;
-  min-height: 64px;
-  flex-shrink: 0;
-}
-
-.left-menu-logo-wrapper--mini {
-  padding: 10px 8px;
-}
-
-.left-menu-logo {
-  width: 120px;
-  height: 60px;
-}
-
-.left-menu-logo--mini {
-  width: 36px;
-  height: 36px;
-}
-
 .left-menu-items-wrapper {
   position: relative;
   flex: 1;
   overflow: hidden;
-  border-radius: 0 0 8px 8px;
+  border-radius: 8px;
 
   &::before {
     content: "";
@@ -380,11 +302,4 @@ onMounted(() => {
   padding-bottom: 6px;
 }
 
-.toggle-button-wrapper {
-  transition: transform 0.3s ease;
-}
-
-.toggle-button-wrapper:hover {
-  transform: scale(1.1);
-}
 </style>

+ 0 - 2
src/components/layout/LeftMenuLayoutMobile.vue

@@ -22,7 +22,6 @@
         <q-list class="column no-wrap">
           <template v-for="item in navigation_store.navigationItems">
             <template v-if="item.permission">
-              <!-- Single Menu -->
               <q-item
                 v-if="item.type === 'single'"
                 :key="item.name"
@@ -38,7 +37,6 @@
                 </q-item-section>
                 <q-item-section>{{ $t(item.title) }}</q-item-section>
               </q-item>
-              <!-- Expansive Menu with children -->
               <q-expansion-item
                 v-else
                 :key="item.icon"

+ 14 - 88
src/components/layout/LeftMenuLayoutParceiro.vue

@@ -8,41 +8,11 @@
     :width="235"
     :mini-width="64"
     :breakpoint="500"
-    :mini="miniState"
+    :mini="navigation_store.miniState"
     :behavior="'desktop'"
     class="left-menu-drawer"
   >
     <div class="column full-height no-wrap left-menu-inner">
-      <div class="left-menu-logo-wrapper" :class="{ 'left-menu-logo-wrapper--mini': miniState }">
-        <q-img
-          v-if="!miniState"
-          :src="Logo"
-          fit="contain"
-          class="left-menu-logo"
-        />
-        <q-img
-          v-else
-          :src="LogoSmall"
-          fit="contain"
-          class="left-menu-logo--mini"
-        />
-      </div>
-      <div
-        v-if="!$q.screen.lt.md"
-        class="toggle-button-wrapper absolute"
-        style="top: 2px; right: -32px; z-index: 1"
-      >
-        <div @click="miniState = !miniState">
-          <q-icon
-            size="sm"
-            :name="miniState ? 'mdi-page-layout-sidebar-right' : 'mdi-page-layout-sidebar-left'"
-            color="violet-normal"
-          />
-          <q-tooltip anchor="center right" self="center left" :offset="[10, 10]">
-            {{ miniState ? $t('ui.navigation.expand_menu') : $t('ui.navigation.collapse_menu') }}
-          </q-tooltip>
-        </div>
-      </div>
       <div class="left-menu-items-wrapper column full-height no-wrap">
         <div class="left-menu-bg-overlay" />
         <div class="left-menu-bg-tint" />
@@ -62,16 +32,16 @@
                 <q-item-section avatar>
                   <q-icon :name="item.icon" class="left-menu-icon" />
                 </q-item-section>
-                <q-item-section v-if="!miniState">{{ $t(item.title) }}</q-item-section>
+                <q-item-section v-if="!navigation_store.miniState">{{ $t(item.title) }}</q-item-section>
                 <q-tooltip
-                  v-if="miniState"
+                  v-if="navigation_store.miniState"
                   anchor="center right"
                   self="center left"
                   :offset="[10, 10]"
                 >{{ $t(item.title) }}</q-tooltip>
               </q-item>
               <div v-else>
-                <template v-if="!miniState">
+                <template v-if="!navigation_store.miniState">
                   <q-expansion-item
                     v-model="isExpasionItemExpanded[index]"
                     :class="{ 'menu-selected': childrenAreActive(item.childrens) && !isExpasionItemExpanded[index] }"
@@ -145,8 +115,8 @@
             <q-item-section avatar >
               <q-icon name="logout" class="left-menu-icon left-menu-logout__icon"/>
             </q-item-section>
-            <q-item-section v-if="!miniState" class="text-violet-normal">{{ $t('auth.logout') }}</q-item-section>
-            <q-tooltip v-if="miniState" anchor="center right" self="center left" :offset="[10, 10]">
+            <q-item-section v-if="!navigation_store.miniState" class="text-violet-normal">{{ $t('auth.logout') }}</q-item-section>
+            <q-tooltip v-if="navigation_store.miniState" anchor="center right" self="center left" :offset="[10, 10]">
               {{ $t('auth.logout') }}
             </q-tooltip>
           </q-item>
@@ -154,9 +124,9 @@
           <q-item v-ripple class="left-menu-item" clickable @click="openUrl('https://softpar.inf.br')">
             <div class="flex full-width justify-center">
               <q-img
-                :src="miniState || $q.screen.lt.md ? LogoSoftparMini : $q.dark.isActive ? LogoSoftparLight : LogoSoftparDark"
+                :src="navigation_store.miniState || $q.screen.lt.md ? LogoSoftparMini : $q.dark.isActive ? LogoSoftparLight : LogoSoftparDark"
                 style="height: 30px"
-                :style="miniState || $q.screen.lt.md ? 'max-width: 48px; width: 48px' : 'max-width: 100px; width: 100%'"
+                :style="navigation_store.miniState || $q.screen.lt.md ? 'max-width: 48px; width: 48px' : 'max-width: 100px; width: 100%'"
               />
             </div>
           </q-item>
@@ -166,7 +136,7 @@
           class="full-width text-center q-pb-xs cursor-pointer left-menu-version"
           @click="gotoVersionPage()"
         >
-          <span v-if="!(miniState || $q.screen.lt.md)" class="text-caption text-weight-light">
+          <span v-if="!(navigation_store.miniState || $q.screen.lt.md)" class="text-caption text-weight-light">
             {{ $t('common.terms.version') + ' ' + version }}
           </span>
           <span v-else class="text-caption text-weight-light">{{ version }}</span>
@@ -177,15 +147,13 @@
   </q-drawer>
 </template>
 <script setup>
-import { ref, watch, watchEffect, onMounted } from "vue";
+import { ref, watchEffect, onMounted } from "vue";
 import { useAuth } from "src/composables/useAuth";
 import { useRouter, useRoute } from "vue-router";
 import { navigationStore } from "src/stores/navigation";
-import { useQuasar, Cookies } from "quasar";
+import { useQuasar } from "quasar";
 import { version } from "src/../package.json";
 
-import Logo from "src/assets/logo_serprati_parceiro.svg";
-import LogoSmall from "src/assets/logo_serprati_parceiro_small.svg";
 import LogoSoftparLight from "src/assets/softpar_logo_light.svg";
 import LogoSoftparDark from "src/assets/softpar_logo_dark.svg";
 import LogoSoftparMini from "src/assets/softpar_logo_mini.svg";
@@ -196,13 +164,6 @@ const route = useRoute();
 const navigation_store = navigationStore();
 const $q = useQuasar();
 
-// const systemTheme = window.matchMedia("(prefers-color-scheme: dark)").matches
-//   ? "dark"
-//   : "light";
-// const systemTheme = "light";
-const miniStateCookies = Cookies.get("miniState");
-const miniState = ref(miniStateCookies === "true" ? true : false);
-
 const childrenAreActive = (children) => {
   if (!Array.isArray(children) || children.length === 0) {
     return false;
@@ -214,8 +175,8 @@ const isExpasionItemExpanded = ref([]);
 
 watchEffect(() => {
   if ($q.screen.lt.md) {
-    miniState.value = true;
-    isExpasionItemExpanded.value.forEach((expansion, index) => {
+    navigation_store.miniState = true;
+    isExpasionItemExpanded.value.forEach((_, index) => {
       isExpasionItemExpanded.value[index] = false;
     });
   }
@@ -234,10 +195,6 @@ const gotoVersionPage = () => {
   router.push({ name: "SystemVersionsPage" });
 };
 
-watch(miniState, () => {
-  Cookies.set("miniState", miniState.value, { path: "/", sameSite: "Lax" });
-});
-
 onMounted(() => {
   navigation_store.navigationItems.forEach((item, index) => {
     if (childrenAreActive(item.childrens)) {
@@ -266,35 +223,11 @@ onMounted(() => {
   background: transparent;
 }
 
-.left-menu-logo-wrapper {
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  background-color: vars.$violet-normal;
-  border-radius: 8px 8px 0 0;
-  min-height: 64px;
-  flex-shrink: 0;
-}
-
-.left-menu-logo-wrapper--mini {
-  padding: 10px 8px;
-}
-
-.left-menu-logo {
-  width: 120px;
-  height: 60px;
-}
-
-.left-menu-logo--mini {
-  width: 36px;
-  height: 36px;
-}
-
 .left-menu-items-wrapper {
   position: relative;
   flex: 1;
   overflow: hidden;
-  border-radius: 0 0 8px 8px;
+  border-radius: 8px;
 
   &::before {
     content: "";
@@ -372,11 +305,4 @@ onMounted(() => {
   padding-bottom: 6px;
 }
 
-.toggle-button-wrapper {
-  transition: transform 0.3s ease;
-}
-
-.toggle-button-wrapper:hover {
-  transform: scale(1.1);
-}
 </style>

+ 7 - 2
src/layouts/MainLayout.vue

@@ -4,6 +4,10 @@
     <LeftMenuLayoutAssociado v-if="!$q.screen.lt.sm && store.userTipo == 'associado'" />
     <LeftMenuLayoutParceiro v-if="!$q.screen.lt.sm && store.userTipo == 'parceiro'" />
     <LeftMenuLayoutMobile v-if="$q.screen.lt.sm" v-model="leftDrawerOpen" />
+    <q-header v-if="!$q.screen.lt.sm" elevated style="height: 52px">
+      <AppHeader />
+    </q-header>
+
     <q-header v-if="$q.screen.lt.sm" class="bg-transparent q-pa-sm">
       <q-toolbar
         class="flex justify-between bg-surface"
@@ -33,13 +37,13 @@
       </q-toolbar>
     </q-header>
     <q-page-container>
-      <q-page>
+      <q-page class="bg-violet-light">
         <q-scroll-area
           ref="scrollAreaRef"
           :style="
             $q.screen.lt.sm
               ? 'height: calc(100dvh - 68px - env(safe-area-inset-top)) !important;'
-              : 'height: calc(100dvh - env(safe-area-inset-top)) !important;'
+              : 'height: calc(100dvh - 52px - env(safe-area-inset-top)) !important;'
           "
         >
           <router-view v-slot="{ Component }">
@@ -67,6 +71,7 @@ import LeftMenuLayoutAdministrador from "src/components/layout/LeftMenuLayoutAdm
 import LeftMenuLayoutAssociado from "src/components/layout/LeftMenuLayoutAssociado.vue";
 import LeftMenuLayoutParceiro from "src/components/layout/LeftMenuLayoutParceiro.vue";
 import LeftMenuLayoutMobile from "src/components/layout/LeftMenuLayoutMobile.vue";
+import AppHeader from "src/components/layout/AppHeader.vue";
 
 const store = userStore();
 const router = useRouter();

+ 47 - 16
src/stores/navigation.js

@@ -1,9 +1,20 @@
 import { defineStore } from "pinia";
-import { computed } from "vue";
+import { ref, computed, watch } from "vue";
+import { Cookies } from "quasar";
 import { permissionStore } from "src/stores/permission";
 import { userStore } from "src/stores/user";
 
 export const navigationStore = defineStore("navigation", () => {
+  const miniState = ref(Cookies.get("miniState") === "true");
+
+  const toggleMini = () => {
+    miniState.value = !miniState.value;
+  };
+
+  watch(miniState, (val) => {
+    Cookies.set("miniState", val, { path: "/", sameSite: "Lax" });
+  });
+
   const navigationStructure = Object.freeze([
     {
       type: "single",
@@ -77,40 +88,58 @@ export const navigationStore = defineStore("navigation", () => {
       permissionScope: "associado.perfil",
       allowedTypes: ["associado"],
     },
+    // {
+    //   type: "single",
+    //   title: "ui.navigation.carteirinha",
+    //   name: "CarteirinhaPage",
+    //   icon: "mdi-card-account-details-outline",
+    //   permission: false,
+    //   permissionScope: "associado.carteirinha",
+    //   allowedTypes: ["associado"],
+    // },
     {
       type: "single",
-      title: "ui.navigation.carteirinha",
-      name: "CarteirinhaPage",
-      icon: "mdi-card-account-details-outline",
+      title: "ui.navigation.convenios",
+      name: "ConveniosPage",
+      icon: "mdi-heart-outline",
       permission: false,
-      permissionScope: "associado.carteirinha",
+      permissionScope: "associado.convenio",
       allowedTypes: ["associado"],
     },
     {
       type: "single",
-      title: "ui.navigation.convenios",
-      name: "ConveniosPage",
+      title: "ui.navigation.loja",
+      name: "AssociadoLojaPage",
+      icon: "mdi-cart-outline",
+      permission: false,
+      permissionScope: "associado.loja",
+      allowedTypes: ["associado"],
+    },
+    {
+      type: "single",
+      title: "ui.navigation.meus_interesses",
+      name: "AssociadoInteressesPage",
       icon: "mdi-heart-outline",
       permission: false,
-      permissionScope: "associado.convenio",
+      permissionScope: "associado.interesses",
       allowedTypes: ["associado"],
     },
     {
       type: "single",
-      title: "ui.navigation.notifications",
-      name: "NotificacoesPage",
-      icon: "mdi-bell-outline",
+      title: "ui.navigation.meus_agendamentos",
+      name: "AssociadoAgendamentosPage",
+      icon: "mdi-calendar-clock-outline",
       permission: false,
-      permissionScope: "notificacao",
+      permissionScope: "associado.agendamento",
       allowedTypes: ["associado"],
     },
     {
       type: "single",
-      title: "ui.navigation.dependentes",
-      name: "DependentesPage",
-      icon: "mdi-account-multiple-outline",
+      title: "ui.navigation.notifications",
+      name: "NotificacoesPage",
+      icon: "mdi-bell-outline",
       permission: false,
-      permissionScope: "associado.dependente",
+      permissionScope: "notificacao",
       allowedTypes: ["associado"],
     },
     {
@@ -190,6 +219,8 @@ export const navigationStore = defineStore("navigation", () => {
   const navigationItems = computed(() => getNavigationAccess());
 
   return {
+    miniState,
+    toggleMini,
     navigationItems,
   };
 });