DashboardPage.vue 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. <template>
  2. <q-page class="dashboard-page bg-page">
  3. <template v-if="loading">
  4. <div
  5. class="bg-surface full-width items-center justify-center row"
  6. style="height: 80vh"
  7. >
  8. <q-spinner-dots color="primary" />
  9. </div>
  10. </template>
  11. <template v-else>
  12. <q-pull-to-refresh color="primary" @refresh="onRefresh">
  13. <DashboardHeaderBar :data="headerBar" :notifications="notifications" />
  14. <DashboardSummaryInfos :data="summaryInfos" />
  15. <DashboardPendingApproval v-if="isPendingProvider" />
  16. <template v-else>
  17. <DashboardPriceSuggest :data="priceSuggestion" />
  18. <DashboardTodayServices
  19. v-if="todayServices?.length > 0"
  20. :data="todayServices"
  21. @refresh="loadDashboard"
  22. @rate="openRatingDialog"
  23. @view-details="openScheduleOnCalendar"
  24. />
  25. <DashboardScrollAreaSchedules />
  26. <DashboardSolicitations
  27. v-if="solicitations?.length > 0"
  28. :data="solicitations"
  29. @accept="(item) => openDetailsDialog(item)"
  30. @reject="(item) => openDetailsDialog(item, 'confirm-reject')"
  31. @view-details="(item) => openDetailsDialog(item)"
  32. />
  33. <DashboardPendingConfirmation
  34. v-if="pendingConfirmation.length > 0"
  35. :data="pendingConfirmation"
  36. @view-details="(item) => openNextScheduleDialog(item)"
  37. />
  38. <DashboardNextSchedules
  39. :data="nextSchedules"
  40. @view-details="(item) => openNextScheduleDialog(item)"
  41. />
  42. <DashboardOpportunities :data="opportunities" />
  43. </template>
  44. </q-pull-to-refresh>
  45. </template>
  46. </q-page>
  47. </template>
  48. <script setup>
  49. import { dadosDashboard } from "src/api/dashboard";
  50. import { onMounted, onUnmounted, ref, watch } from "vue";
  51. import { acceptServicePackage, rejectServicePackage, updateScheduleStatus } from "src/api/schedule";
  52. import { useI18n } from "vue-i18n";
  53. import { useQuasar } from "quasar";
  54. import { useAuth } from "src/composables/useAuth";
  55. import { useProviderApproval } from "src/composables/useProviderApproval";
  56. import { useRoute, useRouter } from "vue-router";
  57. import DashboardHeaderBar from "src/components/dashboard/DashboardHeaderBar.vue";
  58. import DashboardNextSchedules from "src/components/dashboard/DashboardNextSchedules.vue";
  59. import DashboardOpportunities from "src/components/dashboard/DashboardOpportunities.vue";
  60. import DashboardPendingApproval from "src/components/dashboard/DashboardPendingApproval.vue";
  61. import DashboardPriceSuggest from "src/components/dashboard/DashboardPriceSuggest.vue";
  62. import DashboardScrollAreaSchedules from "src/components/dashboard/DashboardScrollAreaSchedules.vue";
  63. import DashboardSolicitations from "src/components/dashboard/DashboardSolicitations.vue";
  64. import DashboardSummaryInfos from "src/components/dashboard/DashboardSummaryInfos.vue";
  65. import DashboardTodayServices from "src/components/dashboard/DashboardTodayServices.vue";
  66. import NextSchedulesDetailsDialog from "src/components/dashboard/NextSchedulesDetailsDialog.vue";
  67. import ScheduleRatingDialog from "src/components/dashboard/ScheduleRatingDialog.vue";
  68. import SolicitationDetailsDialog from "src/components/dashboard/SolicitationDetailsDialog.vue";
  69. import ProposalAcceptedDialog from "src/components/dashboard/ProposalAcceptedDialog.vue";
  70. import OpportunityDialog from "src/pages/opportunities/components/OpportunityDialog.vue";
  71. import DashboardPendingConfirmation from "src/components/dashboard/DashboardPendingConfirmation.vue";
  72. const $q = useQuasar();
  73. const router = useRouter();
  74. const route = useRoute();
  75. const { t } = useI18n();
  76. const { refreshApprovalStatus } = useAuth();
  77. const { isPendingProvider } = useProviderApproval();
  78. // Enquanto o cadastro está em análise, reconsulta a aprovação periodicamente
  79. // para liberar a dashboard completa sem exigir novo login.
  80. const APPROVAL_POLL_INTERVAL_MS = 3 * 60 * 1000;
  81. let approvalPollId = null;
  82. const headerBar = ref({});
  83. const loading = ref(true);
  84. const nextSchedules = ref([]);
  85. const notifications = ref([]);
  86. const opportunities = ref([]);
  87. const priceSuggestion = ref({});
  88. const solicitations = ref([]);
  89. const summaryInfos = ref({});
  90. const todayServices = ref([]);
  91. const pendingConfirmation = ref([]);
  92. const handleScheduleAction = async (id, status, ids = [], servicePackageId = null) => {
  93. try {
  94. if (servicePackageId && status === "accepted") {
  95. await acceptServicePackage(servicePackageId);
  96. } else if (servicePackageId && status === "rejected") {
  97. await rejectServicePackage(servicePackageId);
  98. } else {
  99. const scheduleIds = ids.length ? ids : [id];
  100. await Promise.all(
  101. scheduleIds.map((scheduleId) => updateScheduleStatus(scheduleId, status)),
  102. );
  103. }
  104. } catch (e) {
  105. console.log(e);
  106. } finally {
  107. await loadDashboard();
  108. }
  109. };
  110. const loadDashboard = async () => {
  111. const response = await dadosDashboard();
  112. if (response) {
  113. headerBar.value = response.headerBar;
  114. nextSchedules.value = response.nextSchedules ?? [];
  115. notifications.value = response.notifications ?? [];
  116. opportunities.value = response.opportunities ?? [];
  117. priceSuggestion.value = response.priceSuggested;
  118. solicitations.value = response.solicitations ?? [];
  119. summaryInfos.value = response.summaryInfos;
  120. pendingConfirmation.value = response.pendingConfirmation ?? [];
  121. todayServices.value = response.todayServices ?? [];
  122. }
  123. };
  124. const checkApprovalStatus = async () => {
  125. if (!isPendingProvider.value) {
  126. return false;
  127. }
  128. try {
  129. const approved = await refreshApprovalStatus();
  130. if (!approved) {
  131. return false;
  132. }
  133. stopApprovalPolling();
  134. $q.notify({
  135. message: t("provider.dashboard.pending_approval.approved"),
  136. type: "positive",
  137. });
  138. await loadDashboard();
  139. return true;
  140. } catch (error) {
  141. console.error(error);
  142. return false;
  143. }
  144. };
  145. const startApprovalPolling = () => {
  146. if (approvalPollId || !isPendingProvider.value) {
  147. return;
  148. }
  149. approvalPollId = setInterval(checkApprovalStatus, APPROVAL_POLL_INTERVAL_MS);
  150. };
  151. const stopApprovalPolling = () => {
  152. if (!approvalPollId) {
  153. return;
  154. }
  155. clearInterval(approvalPollId);
  156. approvalPollId = null;
  157. };
  158. const onRefresh = async (done) => {
  159. try {
  160. const approved = await checkApprovalStatus();
  161. if (!approved) {
  162. await loadDashboard();
  163. }
  164. } finally {
  165. done();
  166. }
  167. };
  168. const openDetailsDialog = (solicitation, initialView = "details") => {
  169. $q.dialog({
  170. component: SolicitationDetailsDialog,
  171. componentProps: { initialView, solicitation },
  172. }).onOk(async ({ action, id, ids, service_package_id } = {}) => {
  173. if (action === "cancelled") {
  174. await loadDashboard();
  175. return;
  176. }
  177. await handleScheduleAction(
  178. id,
  179. action === "accept" ? "accepted" : "rejected",
  180. ids,
  181. service_package_id,
  182. );
  183. if (action === "accept") {
  184. $q.dialog({
  185. component: ProposalAcceptedDialog,
  186. });
  187. }
  188. });
  189. };
  190. const openNextScheduleDialog = (schedule) => {
  191. $q.dialog({
  192. component: NextSchedulesDetailsDialog,
  193. componentProps: { schedule },
  194. }).onOk(async ({ action }) => {
  195. if (action === "cancelled") {
  196. await loadDashboard();
  197. }
  198. });
  199. };
  200. const openScheduleOnCalendar = (schedule) => {
  201. router.push({
  202. name: "CalendarPage",
  203. query: { scheduleId: schedule.id },
  204. });
  205. };
  206. const openRatingDialog = (schedule) => {
  207. $q.dialog({
  208. component: ScheduleRatingDialog,
  209. componentProps: { schedule },
  210. }).onOk(() => {
  211. loadDashboard();
  212. });
  213. };
  214. watch(
  215. () => route.query.approved,
  216. async (val) => {
  217. if (val !== "true") {
  218. return;
  219. }
  220. stopApprovalPolling();
  221. if (!loading.value) {
  222. await loadDashboard();
  223. }
  224. $q.notify({
  225. message: t("provider.dashboard.pending_approval.approved"),
  226. type: "positive",
  227. });
  228. router.replace({ path: route.path, query: {} });
  229. },
  230. { immediate: true },
  231. );
  232. watch(
  233. () => route.query.showSuccessModal,
  234. async (val) => {
  235. if (val === "true") {
  236. if (!loading.value) {
  237. await loadDashboard();
  238. }
  239. $q.dialog({ component: OpportunityDialog }).onDismiss(() => {
  240. router.replace({ path: route.path, query: {} });
  241. });
  242. }
  243. },
  244. { immediate: true },
  245. );
  246. onMounted(async () => {
  247. await loadDashboard();
  248. loading.value = false;
  249. startApprovalPolling();
  250. });
  251. onUnmounted(() => {
  252. stopApprovalPolling();
  253. });
  254. </script>
  255. <style scoped>
  256. .dashboard-page {
  257. box-sizing: border-box;
  258. min-height: 100%;
  259. width: 100%;
  260. }
  261. </style>