MainLayout.vue 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. <template>
  2. <q-layout class="main-layout relative" view="hHh lpR fFf">
  3. <q-header
  4. class="system-bar-spacer"
  5. :style="{ height: 'env(safe-area-inset-top)' }"
  6. >
  7. </q-header>
  8. <q-page-container>
  9. <q-page class="bg-surface main-layout-page" style="overflow: hidden;">
  10. <q-scroll-area
  11. ref="scrollAreaRef"
  12. class="main-layout-scroll-area"
  13. :style="scrollAreaHeight"
  14. :content-style="{ width: '100%', maxWidth: '100%', overflowX: 'hidden', boxSizing: 'border-box' }"
  15. :content-active-style="{ width: '100%', maxWidth: '100%', overflowX: 'hidden', boxSizing: 'border-box' }"
  16. >
  17. <router-view v-slot="{ Component }">
  18. <Transition mode="out-in">
  19. <component
  20. :is="Component"
  21. class="main-layout-view"
  22. />
  23. </Transition>
  24. </router-view>
  25. </q-scroll-area>
  26. </q-page>
  27. </q-page-container>
  28. <q-footer class="provider-bottom-nav bg-white">
  29. <nav class="provider-bottom-nav-inner">
  30. <component
  31. :is="item.disabled ? 'div' : 'router-link'"
  32. v-for="item in navItems"
  33. :key="item.name"
  34. :class="{
  35. 'provider-bottom-nav-item--active': isNavItemActive(item),
  36. 'provider-bottom-nav-item--disabled': item.disabled,
  37. }"
  38. :to="item.disabled ? undefined : { name: item.name }"
  39. class="provider-bottom-nav-item"
  40. >
  41. <q-icon :name="item.icon" class="provider-bottom-nav-icon font30" />
  42. <span
  43. class="provider-bottom-nav-label font12"
  44. :class="isNavItemActive(item) ? 'fontbold' : 'fontregular'"
  45. >
  46. {{ item.label }}
  47. </span>
  48. </component>
  49. </nav>
  50. <div class="system-bar-spacer provider-bottom-nav-safe-area"></div>
  51. </q-footer>
  52. </q-layout>
  53. </template>
  54. <script setup>
  55. import { computed, onMounted, useTemplateRef, watch } from "vue";
  56. import { useI18n } from "vue-i18n";
  57. import { useQuasar } from "quasar";
  58. import { useRoute } from "vue-router";
  59. import { useProviderApproval } from "src/composables/useProviderApproval";
  60. import { userStore } from "src/stores/user";
  61. import MissingBankDataDialog from "src/components/profile/MissingBankDataDialog.vue";
  62. import ProfileBankDataDialog from "src/components/profile/ProfileBankDataDialog.vue";
  63. defineOptions({
  64. name: "MainLayout",
  65. });
  66. const route = useRoute();
  67. const scrollAreaRef = useTemplateRef("scrollAreaRef");
  68. const { t } = useI18n();
  69. const $q = useQuasar();
  70. const user = userStore();
  71. const { isPendingProvider } = useProviderApproval();
  72. let oldValue = route.path;
  73. let missingBankDataAlertShown = false;
  74. const navItems = computed(() => [
  75. {
  76. name: "DashboardPage",
  77. label: t('nav.home'),
  78. icon: "mdi-home-outline",
  79. disabled: false,
  80. },
  81. {
  82. name: "PaymentsPage",
  83. label: t('nav.payments'),
  84. icon: "mdi-credit-card-outline",
  85. disabled: isPendingProvider.value,
  86. },
  87. {
  88. name: "CalendarPage",
  89. label: t('nav.agenda'),
  90. icon: "mdi-calendar-blank-outline",
  91. disabled: isPendingProvider.value,
  92. },
  93. {
  94. name: "ProfilePage",
  95. label: t('nav.profile'),
  96. icon: "mdi-account-circle-outline",
  97. disabled: false,
  98. },
  99. ]);
  100. const scrollAreaHeight = computed(() => {
  101. return 'height: calc(100dvh - env(safe-area-inset-top) - 60px - env(safe-area-inset-bottom)) !important;';
  102. });
  103. const checkMissingBankData = async () => {
  104. if (!user.user?.provider && !user.user?.provider_id) {
  105. return;
  106. }
  107. // Cadastro em análise não edita dados bancários.
  108. if (isPendingProvider.value) {
  109. return;
  110. }
  111. try {
  112. await user.fetchUser();
  113. } catch (error) {
  114. console.error('Erro ao carregar dados do prestador:', error);
  115. }
  116. openMissingBankDataAlert();
  117. };
  118. const hasCompleteBankAccount = () => Boolean(user.user?.provider?.has_primary_bank_account);
  119. const isNavItemActive = (item) => route.name === item.name;
  120. const openBankDataDialog = () => {
  121. $q.dialog({
  122. component: ProfileBankDataDialog,
  123. });
  124. };
  125. const openMissingBankDataAlert = () => {
  126. if (missingBankDataAlertShown || hasCompleteBankAccount()) {
  127. return;
  128. }
  129. missingBankDataAlertShown = true;
  130. $q.dialog({
  131. component: MissingBankDataDialog,
  132. }).onOk(openBankDataDialog);
  133. };
  134. watch(
  135. () => route.path,
  136. (value) => {
  137. if (oldValue !== value) {
  138. scrollAreaRef.value?.setScrollPosition("vertical", 0, 0);
  139. scrollAreaRef.value?.setScrollPosition("horizontal", 0, 0);
  140. }
  141. oldValue = value;
  142. }
  143. );
  144. onMounted(() => {
  145. checkMissingBankData();
  146. });
  147. </script>
  148. <style scoped>
  149. .main-layout {
  150. width: 100%;
  151. max-width: 100%;
  152. overflow-x: hidden;
  153. }
  154. .main-layout-page {
  155. width: 100%;
  156. max-width: 100%;
  157. overflow: hidden;
  158. }
  159. .main-layout-scroll-area {
  160. width: 100%;
  161. max-width: 100%;
  162. overflow: hidden;
  163. }
  164. .main-layout-view {
  165. width: 100%;
  166. max-width: 100%;
  167. min-width: 0;
  168. padding: 0 !important;
  169. box-sizing: border-box;
  170. overflow-x: hidden;
  171. }
  172. .v-enter-active {
  173. opacity: 1;
  174. transition: all 0.15s ease-in;
  175. }
  176. .v-leave-active {
  177. opacity: 1;
  178. transition: all 0.15s ease-out;
  179. }
  180. .v-enter-from,
  181. .v-leave-to {
  182. opacity: 0;
  183. transition: all 0.15s ease-in;
  184. }
  185. .v-leave-to {
  186. transition: all 0.15s ease-out;
  187. }
  188. .provider-bottom-nav {
  189. background: #ffffff;
  190. box-shadow: 0 -12px 30px rgba(38, 27, 52, 0.1);
  191. }
  192. .provider-bottom-nav-inner {
  193. display: grid;
  194. grid-template-columns: repeat(4, minmax(0, 1fr));
  195. align-items: end;
  196. height: 60px;
  197. }
  198. .provider-bottom-nav-safe-area {
  199. height: env(safe-area-inset-bottom, 0px);
  200. }
  201. .provider-bottom-nav-item {
  202. display: flex;
  203. flex-direction: column;
  204. align-items: center;
  205. justify-content: flex-end;
  206. gap: 2px;
  207. min-height: 40px;
  208. color: #a1a1a1;
  209. text-decoration: none;
  210. transition: color 0.2s ease;
  211. }
  212. .provider-bottom-nav-item--active {
  213. color: #ff00ea;
  214. }
  215. .provider-bottom-nav-item--disabled {
  216. color: #d4d4d4;
  217. cursor: default;
  218. pointer-events: none;
  219. }
  220. .provider-bottom-nav-icon {
  221. font-size: 24px;
  222. line-height: 1;
  223. }
  224. .provider-bottom-nav-label {
  225. line-height: 1.1;
  226. letter-spacing: -0.02em;
  227. }
  228. </style>