DashboardPage.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. <template>
  2. <q-page class="dashboard-page bg-page">
  3. <template v-if="loading">
  4. <div class="row items-center justify-center full-width bg-surface" style="height: 80vh">
  5. <q-spinner-dots color="primary" />
  6. </div>
  7. </template>
  8. <template v-else>
  9. <q-pull-to-refresh color="primary" @refresh="onRefresh">
  10. <DashboardHeaderBar
  11. :data="headerBar"
  12. :notifications="notifications"
  13. />
  14. <DashboardRegistrationIncomplete v-if="!registrationComplete" />
  15. <DashboardSummaryInfos v-else :data="summaryInfos" />
  16. <DashboardPaymentIncomplete v-if="showPaymentBanner" />
  17. <AddressIncompleteBanner v-if="!hasLocation" @resolved="reloadDashboard" />
  18. <DashboardPendingCustomSchedules v-if="customSchedulesNoProposals.length > 0" />
  19. <DashboardClientProposals v-if="clientProposals.length > 0" :data="clientProposals" @refresh-data="reloadDashboard" />
  20. <DashboardPendingSchedules
  21. v-if="pendingServicePackages.length > 0"
  22. progress-class="progress-fill--urgent"
  23. :data="pendingServicePackages"
  24. :type="'servicePackage'"
  25. @view-details="openServicePackagePaymentDialog"
  26. />
  27. <DashboardPendingSchedules
  28. v-if="pendingRequests.length > 0"
  29. progress-class="progress-fill--loop"
  30. :data="pendingRequests"
  31. @cancel="openCancelRequestDialog"
  32. @view-details="openPackageDetailsDialog"
  33. />
  34. <DashboardTodaySchedules
  35. v-if="todaySchedules.length > 0"
  36. :data="todaySchedules"
  37. @rate="openRatingDialog"
  38. @view-details="openScheduleOnCalendar"
  39. />
  40. <DashboardScrollAreaSchedules />
  41. <DashboardNextSchedules v-if="nextSchedules.length > 0" :data="nextSchedules" @view-details="openNextScheduleDialog" />
  42. <DashboardLastDoneSchedules v-if="lastDoneSchedules.length > 0" :data="lastDoneSchedules" />
  43. <DashboardFavoriteProviders v-if="favoriteProviders.length > 0" :data="favoriteProviders" />
  44. <DashboardProvidersClose v-if="hasLocation" :data="providersClose" />
  45. </q-pull-to-refresh>
  46. </template>
  47. </q-page>
  48. </template>
  49. <script setup>
  50. import { computed, onMounted, ref } from 'vue';
  51. import { dadosDashboard } from 'src/api/dashboard';
  52. import { LocalStorage, useQuasar } from 'quasar';
  53. import { updateMe } from 'src/api/user';
  54. import { useI18n } from 'vue-i18n';
  55. import { usePaymentStore } from 'src/stores/payment';
  56. import { useRoute, useRouter } from 'vue-router'
  57. import { userStore } from 'src/stores/user';
  58. import AddressIncompleteBanner from 'src/components/shared/AddressIncompleteBanner.vue';
  59. import DashboardClientProposals from 'src/pages/dashboard/components/DashboardClientProposals.vue';
  60. import DashboardFavoriteProviders from 'src/components/dashboard/DashboardFavoriteProviders.vue';
  61. import DashboardHeaderBar from 'src/components/dashboard/DashboardHeaderBar.vue';
  62. import DashboardLastDoneSchedules from 'src/components/dashboard/DashboardLastDoneSchedules.vue';
  63. import DashboardNextSchedules from 'src/components/dashboard/DashboardNextSchedules.vue';
  64. import DashboardPaymentIncomplete from 'src/components/dashboard/DashboardPaymentIncomplete.vue';
  65. import DashboardPendingCustomSchedules from 'src/pages/dashboard/components/DashboardPendingCustomSchedules.vue';
  66. import DashboardPendingSchedules from 'src/components/dashboard/DashboardPendingSchedules.vue';
  67. import PackageScheduleDetailsDialog from 'src/components/dashboard/PackageScheduleDetailsDialog.vue';
  68. import DashboardProvidersClose from 'src/components/dashboard/DashboardProvidersClose.vue';
  69. import DashboardRegistrationIncomplete from 'src/components/dashboard/DashboardRegistrationIncomplete.vue';
  70. import DashboardScrollAreaSchedules from 'src/components/dashboard/DashboardScrollAreaSchedules.vue';
  71. import DashboardSummaryInfos from 'src/components/dashboard/DashboardSummaryInfos.vue';
  72. import DashboardTodaySchedules from 'src/components/dashboard/DashboardTodaySchedules.vue';
  73. import FinalSuccesModal from '../schedules/components/FinalSuccesModal.vue';
  74. import NextSchedulesDetailsDialog from 'src/pages/dashboard/components/schedule/NextSchedulesDetailsDialog.vue';
  75. import ScheduleAcceptedDialog from 'src/pages/dashboard/components/schedule/ScheduleAcceptedDialog.vue';
  76. import ScheduleCancelDialog from 'src/pages/dashboard/components/schedule/ScheduleCancelDialog.vue';
  77. import ScheduleRatingDialog from 'src/pages/dashboard/components/schedule/ScheduleRatingDialog.vue';
  78. const $q = useQuasar();
  79. const paymentStore = usePaymentStore();
  80. const route = useRoute()
  81. const router = useRouter()
  82. const store = userStore();
  83. const { t } = useI18n();
  84. const clientProposals = ref([]);
  85. const customSchedulesNoProposals = ref([]);
  86. const favoriteProviders = ref([]);
  87. const hasLocation = ref(true);
  88. const hasPaymentMethods = ref(true);
  89. const headerBar = ref({});
  90. const lastDoneSchedules = ref([]);
  91. const loading = ref(true);
  92. const nextSchedules = ref([]);
  93. const notifications = ref([]);
  94. const pendingSchedules = ref([]);
  95. const pendingServicePackages = ref([]);
  96. const providersClose = ref([]);
  97. const showPaymentBanner = ref(false);
  98. const successModalKey = ref(
  99. route.query.success ??
  100. (router.currentRoute.value.fullPath.includes('showSuccessModal') ? 'custom_schedule' : null),
  101. );
  102. const summaryInfos = ref({});
  103. const todaySchedules = ref([]);
  104. const pendingRequests = computed(() => {
  105. const seenPackages = new Set();
  106. return pendingSchedules.value.filter((schedule) => {
  107. if (schedule.status !== 'pending') return false;
  108. if (!schedule.service_package_id) return true;
  109. if (seenPackages.has(schedule.service_package_id)) return false;
  110. seenPackages.add(schedule.service_package_id);
  111. return true;
  112. });
  113. });
  114. const openPackageDetailsDialog = (schedule) => {
  115. $q.dialog({
  116. component: PackageScheduleDetailsDialog,
  117. componentProps: { schedule },
  118. });
  119. };
  120. const registrationComplete = computed(() => store.user?.registration_complete ?? true);
  121. const AUTO_SHOWN_PACKAGES_KEY = "sp_payment_popup_shown_ids";
  122. const successModalContents = {
  123. custom_schedule: null,
  124. order_sent: () => ({
  125. titleLine1: t('service_package.sent_title_line1'),
  126. titleLine2: t('service_package.sent_title_line2'),
  127. subtitleBefore: t('service_package.sent_subtitle_before'),
  128. subtitleHighlight: t('service_package.sent_subtitle_highlight'),
  129. subtitleAfter: t('service_package.sent_subtitle_after'),
  130. }),
  131. };
  132. let autoOpeningPackageDialog = false;
  133. const maybeOpenSuccessModal = () => {
  134. if (!successModalKey.value) return;
  135. if (!(successModalKey.value in successModalContents)) {
  136. successModalKey.value = null;
  137. return;
  138. }
  139. const buildContent = successModalContents[successModalKey.value];
  140. successModalKey.value = null;
  141. $q.dialog({
  142. component: FinalSuccesModal,
  143. componentProps: { content: buildContent ? buildContent() : null },
  144. }).onDismiss(() => {
  145. router.replace({ path: route.path, query: {} });
  146. });
  147. };
  148. const openCancelRequestDialog = (schedule) => {
  149. $q.dialog({
  150. component: ScheduleCancelDialog,
  151. componentProps: { schedule },
  152. }).onDismiss(() => {
  153. reloadDashboard(false);
  154. });
  155. };
  156. const openServicePackagePaymentDialog = (servicePackage) => {
  157. paymentStore.setPackage(servicePackage);
  158. $q.dialog({
  159. component: ScheduleAcceptedDialog,
  160. }).onOk(() => {
  161. reloadDashboard();
  162. }).onDismiss(() => {
  163. paymentStore.clearPackage();
  164. });
  165. };
  166. const maybeAutoOpenServicePackagePayment = () => {
  167. if (autoOpeningPackageDialog) return;
  168. const shownIds = LocalStorage.getItem(AUTO_SHOWN_PACKAGES_KEY) ?? [];
  169. const packageToShow = pendingServicePackages.value.find(
  170. (servicePackage) => !shownIds.includes(servicePackage.id),
  171. );
  172. if (!packageToShow) return;
  173. autoOpeningPackageDialog = true;
  174. LocalStorage.set(AUTO_SHOWN_PACKAGES_KEY, [...shownIds, packageToShow.id]);
  175. paymentStore.setPackage(packageToShow);
  176. $q.dialog({
  177. component: ScheduleAcceptedDialog,
  178. })
  179. .onOk(() => {
  180. reloadDashboard();
  181. })
  182. .onDismiss(() => {
  183. autoOpeningPackageDialog = false;
  184. paymentStore.clearPackage();
  185. });
  186. };
  187. const reloadDashboard = async (showLoader = true) => {
  188. if (showLoader) loading.value = true;
  189. const response = await dadosDashboard();
  190. if (response) {
  191. headerBar.value = response.headerBar;
  192. summaryInfos.value = response.summaryInfos;
  193. pendingSchedules.value = response.pendingSchedules ?? [];
  194. pendingServicePackages.value = response.pendingServicePackages ?? [];
  195. nextSchedules.value = response.nextSchedules ?? [];
  196. lastDoneSchedules.value = response.lastDoneSchedules ?? [];
  197. favoriteProviders.value = response.favoriteProviders ?? [];
  198. providersClose.value = response.providersClose ?? [];
  199. clientProposals.value = response.schedulesProposals ?? [];
  200. customSchedulesNoProposals.value = response.customSchedulesNoProposals ?? [];
  201. todaySchedules.value = response.todaySchedules ?? [];
  202. notifications.value = response.notifications ?? [];
  203. hasPaymentMethods.value = response.has_payment_methods ?? true;
  204. hasLocation.value = response.has_location ?? true;
  205. }
  206. loading.value = false;
  207. maybeAutoOpenServicePackagePayment();
  208. };
  209. const onRefresh = async (done) => {
  210. try {
  211. await reloadDashboard(false);
  212. } finally {
  213. done();
  214. }
  215. };
  216. const openNextScheduleDialog = (schedule) => {
  217. $q.dialog({
  218. component: NextSchedulesDetailsDialog,
  219. componentProps: { schedule }
  220. }).onOk(() => {
  221. reloadDashboard();
  222. });
  223. };
  224. const openScheduleOnCalendar = (schedule) => {
  225. router.push({
  226. name: 'CalendarPage',
  227. query: { scheduleId: schedule.id },
  228. });
  229. };
  230. const openRatingDialog = (schedule) => {
  231. $q.dialog({
  232. component: ScheduleRatingDialog,
  233. componentProps: { schedule }
  234. }).onOk(() => {
  235. reloadDashboard()
  236. })
  237. }
  238. onMounted(async () => {
  239. await reloadDashboard();
  240. maybeOpenSuccessModal();
  241. const isFirstAccess = store.user?.client?.first_access ?? false;
  242. showPaymentBanner.value = isFirstAccess && !hasPaymentMethods.value;
  243. if (isFirstAccess) {
  244. updateMe({ first_access: false }).catch(() => {});
  245. store.markFirstAccessSeen();
  246. }
  247. });
  248. </script>
  249. <style scoped>
  250. .dashboard-page {
  251. width: 100%;
  252. min-height: 100%;
  253. box-sizing: border-box;
  254. }
  255. </style>