MainLayout.vue 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. <template>
  2. <q-layout class="relative" view="hHh lpR fFf">
  3. <LeftMenuLayout v-if="$q.screen.gt.sm" />
  4. <LeftMenuLayoutMobile v-else v-model="leftDrawerOpen" />
  5. <q-header v-if="!$q.screen.gt.sm" class="bg-transparent" style="padding: 8px; padding-top: calc(env(safe-area-inset-top) + 8px);">
  6. <q-toolbar class="bg-surface items-center" style="border-radius: 6px !important; padding: 0 8px; min-height: 50px;">
  7. <q-btn dense flat round size="sm" @click="toggleLeftDrawer">
  8. <q-icon name="menu" color="primary" size="28px" />
  9. </q-btn>
  10. <span class="text-subtitle1 text-weight-bold text-primary q-ml-xs">
  11. {{ $t('auth.type.associado') }}
  12. </span>
  13. <q-space />
  14. <q-avatar size="40px" color="violet-normal" text-color="white" style="font-size: 20px; font-weight: 700; cursor: default;">
  15. <img v-if="store.user?.photo_url" :src="store.user.photo_url" style="object-fit: cover; width: 100%; height: 100%;" />
  16. <template v-else>{{ userInitial }}</template>
  17. </q-avatar>
  18. </q-toolbar>
  19. </q-header>
  20. <q-page-container>
  21. <q-page>
  22. <q-scroll-area
  23. ref="scrollAreaRef"
  24. :style="
  25. !$q.screen.gt.sm
  26. ? 'height: calc(100dvh - 68px - env(safe-area-inset-top) - env(safe-area-inset-bottom)) !important; width: 100%; overflow-x: hidden;'
  27. : 'height: calc(100dvh - env(safe-area-inset-bottom)) !important; width: 100%; overflow-x: hidden;'
  28. "
  29. >
  30. <router-view v-slot="{ Component }">
  31. <Transition mode="out-in">
  32. <component
  33. :is="Component"
  34. style="padding: 20px !important; padding-right: 10px !important"
  35. :style="[
  36. !$q.screen.gt.sm ? 'padding-left: 10px !important;' : '',
  37. $q.screen.gt.sm ? 'padding-top: calc(env(safe-area-inset-top) + 20px) !important;' : '',
  38. ]"
  39. />
  40. </Transition>
  41. </router-view>
  42. </q-scroll-area>
  43. </q-page>
  44. </q-page-container>
  45. </q-layout>
  46. </template>
  47. <script setup>
  48. import { ref, computed, onMounted, useTemplateRef, watch } from "vue";
  49. import { useRoute } from "vue-router";
  50. import { useQuasar } from "quasar";
  51. import { userStore } from "src/stores/user";
  52. import { getMyUnreadNotificationsAssociado } from "src/api/notification";
  53. import { getPendingEvaluationAssociado } from "src/api/evaluation";
  54. import UnreadNotificationsDialog from "src/components/UnreadNotificationsDialog.vue";
  55. import CompleteProfileDialog from "src/components/CompleteProfileDialog.vue";
  56. import EvaluationDialog from "src/components/EvaluationDialog.vue";
  57. import LeftMenuLayout from "src/components/layout/LeftMenuLayout.vue";
  58. import LeftMenuLayoutMobile from "src/components/layout/LeftMenuLayoutMobile.vue";
  59. const route = useRoute();
  60. const $q = useQuasar();
  61. const store = userStore();
  62. const leftDrawerOpen = ref(false);
  63. const scrollAreaRef = useTemplateRef("scrollAreaRef");
  64. const userInitial = computed(() => {
  65. const name = store.user?.name || "";
  66. return name.charAt(0).toUpperCase() || "A";
  67. });
  68. let oldValue = route.path;
  69. const toggleLeftDrawer = () => {
  70. leftDrawerOpen.value = !leftDrawerOpen.value;
  71. };
  72. const isProfileIncomplete = (user) => {
  73. return (
  74. !user.terms_accepted_at ||
  75. !user.photo_url ||
  76. !user.name ||
  77. !user.cpf ||
  78. !user.email ||
  79. !user.phone ||
  80. !(user.position_id ?? user.position?.id) ||
  81. !(user.sector_id ?? user.sector?.id)
  82. );
  83. };
  84. const checkUnreadNotifications = async () => {
  85. try {
  86. const unread = await getMyUnreadNotificationsAssociado();
  87. if (unread && unread.length > 0) {
  88. $q.dialog({ component: UnreadNotificationsDialog });
  89. }
  90. } catch {
  91. // silent
  92. }
  93. };
  94. const checkPendingEvaluation = async () => {
  95. try {
  96. const campaign = await getPendingEvaluationAssociado();
  97. if (!campaign) {
  98. await checkUnreadNotifications();
  99. return;
  100. }
  101. $q.dialog({
  102. component: EvaluationDialog,
  103. componentProps: { campaign },
  104. }).onOk(() => checkUnreadNotifications());
  105. } catch {
  106. await checkUnreadNotifications();
  107. }
  108. };
  109. onMounted(async () => {
  110. const user = store.user;
  111. if (user && isProfileIncomplete(user)) {
  112. $q.dialog({ component: CompleteProfileDialog })
  113. .onOk(() => checkPendingEvaluation());
  114. } else {
  115. await checkPendingEvaluation();
  116. }
  117. });
  118. watch(route, (value) => {
  119. if (oldValue.path != value.path) {
  120. scrollAreaRef.value.setScrollPosition("vertical", 0, 0);
  121. scrollAreaRef.value.setScrollPosition("horizontal", 0, 0);
  122. }
  123. oldValue = value.path;
  124. });
  125. </script>
  126. <style scoped>
  127. .v-enter-active {
  128. opacity: 1;
  129. transition: all 0.15s ease-in;
  130. }
  131. .v-leave-active {
  132. opacity: 1;
  133. transition: all 0.15s ease-out;
  134. }
  135. .v-enter-from,
  136. .v-leave-to {
  137. opacity: 0;
  138. transition: all 0.15s ease-in;
  139. }
  140. .v-leave-to {
  141. transition: all 0.15s ease-out;
  142. }
  143. </style>