瀏覽代碼

feat: tela de confirmacao sob medida + correcao botoes reagendar

Gustavo Zanatta 3 周之前
父節點
當前提交
245a32fd61

+ 3 - 1
src/api/servicePackage.js

@@ -21,7 +21,9 @@ export const createServicePackage = async (payload) => {
 };
 
 export const createMyServicePackage = async (payload) => {
-  const { data } = await api.post("/service-packages/me", payload);
+  const { data } = await api.post("/service-packages/me", payload, {
+    skipSuccessNotify: true,
+  });
   return data.payload;
 };
 

+ 1 - 1
src/boot/axios.js

@@ -101,7 +101,7 @@ const errorInterceptor = async (error, router) => {
 };
 
 const successInterceptor = (response) => {
-  if (response.data.message) {
+  if (response.data.message && !response.config?.skipSuccessNotify) {
     Notify.create({
       message: response.data.message,
       type: "positive",

+ 6 - 0
src/i18n/locales/en.json

@@ -758,6 +758,7 @@
     "reviews_title": "Reviews",
     "see_all": "see all",
     "no_reviews": "No reviews found.",
+    "provider_unavailable": "This provider is not available for scheduling right now.",
     "unknown_client": "Client",
     "select_service": "Select service",
     "no_slots_available": "No time slots available for this day.",
@@ -855,6 +856,11 @@
     "submit": "send request",
     "close": "close orders",
     "closed_success": "Orders closed. Wait for provider approval.",
+    "sent_title_line1": "YOUR ORDER WAS SENT",
+    "sent_title_line2": "TO THE PROVIDER!",
+    "sent_subtitle_before": "Wait for the ",
+    "sent_subtitle_highlight": "provider confirmation",
+    "sent_subtitle_after": " to make the payment and complete your booking.",
     "same_provider_required": "All time slots must be from the same provider.",
     "unknown": "No information",
     "multiple_providers": "{count} professionals",

+ 6 - 0
src/i18n/locales/es.json

@@ -755,6 +755,7 @@
     "reviews_title": "Reseñas",
     "see_all": "ver todas",
     "no_reviews": "No se encontraron reseñas.",
+    "provider_unavailable": "Este prestador no está disponible para agendar en este momento.",
     "unknown_client": "Cliente",
     "select_service": "Seleccionar servicio",
     "no_slots_available": "No hay horarios disponibles para este día.",
@@ -852,6 +853,11 @@
     "submit": "enviar solicitud",
     "close": "cerrar pedidos",
     "closed_success": "Pedidos cerrados. Espere la aprobación de los profesionales.",
+    "sent_title_line1": "¡TU PEDIDO FUE ENVIADO",
+    "sent_title_line2": "AL PROFESIONAL!",
+    "sent_subtitle_before": "Espere la ",
+    "sent_subtitle_highlight": "confirmación del profesional",
+    "sent_subtitle_after": " para realizar el pago y concluir su reserva.",
     "same_provider_required": "Todos los horarios deben ser del mismo profesional.",
     "unknown": "Sin información",
     "multiple_providers": "{count} profesionales",

+ 6 - 0
src/i18n/locales/pt.json

@@ -766,6 +766,7 @@
     "reviews_title": "Avaliações",
     "see_all": "ver todas",
     "no_reviews": "Nenhuma avaliação encontrada.",
+    "provider_unavailable": "Este prestador não está disponível para agendamento no momento.",
     "unknown_client": "Cliente",
     "select_service": "Selecionar serviço",
     "no_slots_available": "Não há horários disponíveis para este dia.",
@@ -863,6 +864,11 @@
     "submit": "enviar solicitação",
     "close": "confirmar pedido(s)",
     "closed_success": "Pedidos fechados. Aguarde a aprovação dos profissionais.",
+    "sent_title_line1": "SEU PEDIDO FOI ENVIADO",
+    "sent_title_line2": "PARA O DIARISTA!",
+    "sent_subtitle_before": "Aguarde a ",
+    "sent_subtitle_highlight": "confirmação do profissional",
+    "sent_subtitle_after": " para efetuar o pagamento e concluir o seu agendamento.",
     "same_provider_required": "Todos os horários devem ser do mesmo profissional.",
     "unknown": "Sem informação",
     "multiple_providers": "{count} profissionais",

+ 41 - 11
src/pages/dashboard/DashboardPage.vue

@@ -57,8 +57,9 @@ import DashboardTodaySchedules from 'src/components/dashboard/DashboardTodaySche
 import FinalSuccesModal from '../schedules/components/FinalSuccesModal.vue';
 import DashboardPendingCustomSchedules from 'src/pages/dashboard/components/DashboardPendingCustomSchedules.vue';
 import DashboardClientProposals from 'src/pages/dashboard/components/DashboardClientProposals.vue';
-import { useRouter } from 'vue-router'
+import { useRoute, useRouter } from 'vue-router'
 import { onMounted, ref, computed } from 'vue';
+import { useI18n } from 'vue-i18n';
 import { LocalStorage, useQuasar } from 'quasar';
 import { dadosDashboard } from 'src/api/dashboard';
 import { userStore } from 'src/stores/user';
@@ -66,6 +67,8 @@ import NextSchedulesDetailsDialog from 'src/components/dashboard/NextSchedulesDe
 import ScheduleRatingDialog from 'src/components/dashboard/ScheduleRatingDialog.vue';
 
 const router = useRouter()
+const route = useRoute()
+const { t } = useI18n();
 const $q = useQuasar();
 const store = userStore();
 
@@ -83,9 +86,24 @@ const providersClose = ref([]);
 const todaySchedules = ref([]);
 const notifications = ref([]);
 const loading = ref(true);
-const showSuccessModal = ref(router.currentRoute.value.fullPath.includes('showSuccessModal'));
 const registrationComplete = computed(() => store.user?.registration_complete ?? true);
 
+const successModalKey = ref(
+  route.query.success ??
+    (router.currentRoute.value.fullPath.includes('showSuccessModal') ? 'custom_schedule' : null),
+);
+
+const successModalContents = {
+  custom_schedule: null,
+  order_sent: () => ({
+    titleLine1: t('service_package.sent_title_line1'),
+    titleLine2: t('service_package.sent_title_line2'),
+    subtitleBefore: t('service_package.sent_subtitle_before'),
+    subtitleHighlight: t('service_package.sent_subtitle_highlight'),
+    subtitleAfter: t('service_package.sent_subtitle_after'),
+  }),
+};
+
 const pendingRequests = computed(() => {
   const seenPackages = new Set();
 
@@ -167,20 +185,31 @@ const reloadDashboard = async (showLoader = true) => {
     notifications.value = response.notifications ?? [];
     hasPaymentMethods.value = response.has_payment_methods ?? true;
   }
-  if( showSuccessModal.value == true) {
-    $q.dialog({
-       component: FinalSuccesModal
-    }).onDismiss(() => {
-      showSuccessModal.value = false;
-      router.replace({ path: router.currentRoute.value.path, query: {} });
-    })
-  }
-
   loading.value = false;
 
   maybeAutoOpenServicePackagePayment();
 };
 
+const maybeOpenSuccessModal = () => {
+  if (!successModalKey.value) return;
+
+  if (!(successModalKey.value in successModalContents)) {
+    successModalKey.value = null;
+    return;
+  }
+
+  const buildContent = successModalContents[successModalKey.value];
+
+  successModalKey.value = null;
+
+  $q.dialog({
+    component: FinalSuccesModal,
+    componentProps: { content: buildContent ? buildContent() : null },
+  }).onDismiss(() => {
+    router.replace({ path: route.path, query: {} });
+  });
+};
+
 const onRefresh = async (done) => {
   try {
     await reloadDashboard(false);
@@ -209,6 +238,7 @@ const openRatingDialog = (schedule) => {
 
 onMounted(async () => {
   await reloadDashboard();
+  maybeOpenSuccessModal();
 });
 </script>
 

+ 2 - 0
src/pages/orders/ServicePackagePage.vue

@@ -592,6 +592,8 @@ const submitServicePackage = async () => {
       type: "positive",
       message: t("service_package.closed_success"),
     });
+
+    router.push({ name: "DashboardPage", query: { success: "order_sent" } });
   } catch (error) {
     const message =
       error?.response?.data?.message ??

+ 1 - 1
src/pages/schedules/SobMedidaPage.vue

@@ -321,7 +321,7 @@ const saveFinalOrder = async (payloadFinal) => {
     message: t('sob_medida.success_message')
   })
 
-  router.push('/#showSuccessModal')
+  router.push({ name: 'DashboardPage', query: { success: 'custom_schedule' } })
 }
 
 const increaseQuantity = () => {

+ 22 - 5
src/pages/schedules/components/FinalSuccesModal.vue

@@ -44,17 +44,17 @@
 
         <!-- titulo -->
         <div class="success-title gradient-diarista q-mt-md font16 fontbold">
-          {{ $t('custom_schedule.success_title_line1') }}<br>
-          {{ $t('custom_schedule.success_title_line2') }}
+          {{ titleLine1 }}<br>
+          {{ titleLine2 }}
         </div>
 
         <!-- subtitulo -->
         <div class="success-subtitle q-mt-md">
-          {{ $t('custom_schedule.success_subtitle_before') }}
+          {{ subtitleBefore }}
 
-          <strong>{{ $t('custom_schedule.success_subtitle_highlight') }}</strong>
+          <strong>{{ subtitleHighlight }}</strong>
 
-          {{ $t('custom_schedule.success_subtitle_after') }}
+          {{ subtitleAfter }}
         </div>
       </q-card-section>
     </q-card>
@@ -63,14 +63,31 @@
 
 <script setup>
 import { useDialogPluginComponent } from 'quasar'
+import { computed } from 'vue'
+import { useI18n } from 'vue-i18n'
+
+const props = defineProps({
+  content: {
+    type: Object,
+    default: null
+  }
+})
 
 defineEmits([...useDialogPluginComponent.emits])
 
+const { t } = useI18n()
+
 const {
   dialogRef,
   onDialogHide
 } = useDialogPluginComponent()
 
+const titleLine1 = computed(() => props.content?.titleLine1 ?? t('custom_schedule.success_title_line1'))
+const titleLine2 = computed(() => props.content?.titleLine2 ?? t('custom_schedule.success_title_line2'))
+const subtitleBefore = computed(() => props.content?.subtitleBefore ?? t('custom_schedule.success_subtitle_before'))
+const subtitleHighlight = computed(() => props.content?.subtitleHighlight ?? t('custom_schedule.success_subtitle_highlight'))
+const subtitleAfter = computed(() => props.content?.subtitleAfter ?? t('custom_schedule.success_subtitle_after'))
+
 const handleClose = () => {
   dialogRef.value?.hide()
 }

+ 73 - 24
src/pages/search/components/SchedulingDialog.vue

@@ -43,18 +43,20 @@
                 <div class="col-2">
                   <q-avatar
                     :style="
-                      avatarColors[provider.provider_id % avatarColors.length]
+                      avatarColors[
+                        providerInfo.provider_id % avatarColors.length
+                      ]
                     "
                   >
                     <img
-                      v-if="getProfileMediaUrl(provider)"
+                      v-if="getProfileMediaUrl(providerInfo)"
                       style="object-fit: cover; border-radius: 50%"
-                      :src="getProfileMediaUrl(provider)"
+                      :src="getProfileMediaUrl(providerInfo)"
                     />
 
                     <span v-else>
                       {{
-                        getFirstName(provider.provider_name)
+                        getFirstName(providerInfo.provider_name)
                           ?.slice(0, 2)
                           .toUpperCase() ?? "??"
                       }}
@@ -64,11 +66,11 @@
 
                 <div class="col-3 column">
                   <div class="font12 fontbold text-text">
-                    {{ getFirstName(provider?.provider_name) ?? "—" }}
+                    {{ getFirstName(providerInfo?.provider_name) ?? "—" }}
                   </div>
 
                   <div class="font10 fontmedium text-grey-7">
-                    {{ provider?.district ?? "—" }}
+                    {{ providerDistrict ?? "—" }}
                   </div>
                 </div>
 
@@ -83,11 +85,11 @@
 
                       <span class="font9 fontmedium q-ml-xs">
                         {{
-                          (provider?.average_rating != null
-                            ? Number(provider.average_rating).toFixed(1)
+                          (providerInfo?.average_rating != null
+                            ? Number(providerInfo.average_rating).toFixed(1)
                             : "") +
                           " (" +
-                          (provider?.total_reviews ?? 0) +
+                          (providerInfo?.total_reviews ?? 0) +
                           ")"
                         }}
                       </span>
@@ -101,7 +103,7 @@
                       />
 
                       <span class="font9 fontmedium q-ml-xs">
-                        {{ provider?.total_services ?? 0 }}
+                        {{ providerInfo?.total_services ?? 0 }}
                       </span>
                     </div>
                   </div>
@@ -140,7 +142,7 @@
           </div>
 
           <div
-            v-if="loadingAvailability"
+            v-if="loadingAvailability || loadingProvider"
             class="row items-center justify-center q-py-lg"
           >
             <q-spinner-dots
@@ -271,6 +273,7 @@ import {
 import { date, useDialogPluginComponent, useQuasar } from "quasar";
 import { getClientProviderBlocks } from "src/api/schedule";
 import { getFirstName } from "src/helpers/utils";
+import { getPerfilPrestador } from "src/api/dashboard";
 import { getProfileMediaUrl } from "src/helpers/profileMedia";
 
 import {
@@ -303,12 +306,15 @@ const { dialogRef } = useDialogPluginComponent();
 const servicePackage = useServicePackageStore();
 const store = userStore();
 
+const providerInfo = ref({ ...props.provider });
+
 const blockedDays = ref([]);
 const bookings = ref([]);
 const favoriteId = ref(null);
 const favoriteLoading = ref(false);
 const isFavorite = ref(false);
 const loadingAvailability = ref(true);
+const loadingProvider = ref(true);
 const loadingReviews = ref(true);
 const providerClientBlocks = ref({
   existing_schedules: [],
@@ -322,6 +328,10 @@ const availableWeekDays = computed(() => [
   ...new Set(workingDays.value.map((wd) => wd.day)),
 ]);
 
+const providerDistrict = computed(
+  () => providerInfo.value?.district ?? providerInfo.value?.provider_district,
+);
+
 const blockedDateSet = computed(
   () =>
     new Set(
@@ -350,14 +360,14 @@ const getWeekStart = (dateStr) => {
 const cartHasAnotherProvider = () =>
   servicePackage.items.length > 0 &&
   Number(servicePackage.items[0].provider_id) !==
-    Number(props.provider.provider_id);
+    Number(providerInfo.value.provider_id);
 
 const getServicePackageWeekCount = (selectedDate) => {
   const weekStart = getWeekStart(selectedDate);
 
   return servicePackage.items.filter(
     (item) =>
-      Number(item.provider_id) === Number(props.provider.provider_id) &&
+      Number(item.provider_id) === Number(providerInfo.value.provider_id) &&
       getWeekStart(item.date) === weekStart,
   ).length;
 };
@@ -420,10 +430,10 @@ const loadAvailability = async () => {
     };
 
     const [wd, bd, clientBlocks] = await Promise.all([
-      getProviderWorkingDays(props.provider.provider_id),
-      getProviderBlockedDays(props.provider.provider_id),
+      getProviderWorkingDays(providerInfo.value.provider_id),
+      getProviderBlockedDays(providerInfo.value.provider_id),
       clientId
-        ? getClientProviderBlocks(clientId, props.provider.provider_id)
+        ? getClientProviderBlocks(clientId, providerInfo.value.provider_id)
         : Promise.resolve(defaultClientBlocks),
     ]);
 
@@ -446,6 +456,44 @@ const loadAvailability = async () => {
   }
 };
 
+const loadProviderProfile = async () => {
+  const providerId = props.provider?.provider_id ?? props.provider?.id;
+
+  if (!providerId) {
+    loadingProvider.value = false;
+
+    return;
+  }
+
+  loadingProvider.value = true;
+
+  try {
+    const payload = await getPerfilPrestador(providerId);
+
+    providerInfo.value = { ...props.provider, ...payload };
+  } catch {
+    providerInfo.value = { ...props.provider };
+  } finally {
+    loadingProvider.value = false;
+  }
+
+  const hasAnyPrice = [
+    "daily_price_8h",
+    "daily_price_6h",
+    "daily_price_4h",
+    "daily_price_2h",
+  ].some((key) => providerInfo.value?.[key] != null);
+
+  if (!hasAnyPrice) {
+    $q.notify({
+      message: t("scheduling_page.provider_unavailable"),
+      type: "warning",
+    });
+
+    dialogRef.value?.hide();
+  }
+};
+
 const loadFavoriteState = async () => {
   const clientId = store.user?.client?.id;
   if (!clientId) return;
@@ -453,7 +501,7 @@ const loadFavoriteState = async () => {
   try {
     const favorites = await getClientFavoriteProviders(clientId);
     const match = (favorites ?? []).find(
-      (f) => Number(f.provider_id) === Number(props.provider.provider_id),
+      (f) => Number(f.provider_id) === Number(providerInfo.value.provider_id),
     );
 
     isFavorite.value = !!match;
@@ -468,7 +516,7 @@ const loadReviews = async () => {
   loadingReviews.value = true;
 
   try {
-    const all = await getProviderReceivedReviews(props.provider.provider_id);
+    const all = await getProviderReceivedReviews(providerInfo.value.provider_id);
 
     reviews.value = (all ?? []).slice(0, 10);
   } catch {
@@ -515,7 +563,7 @@ const onDateSelected = (val) => {
   const servicePackageBookingBlocks = servicePackage.items
     .filter(
       (item) =>
-        Number(item.provider_id) === Number(props.provider.provider_id) &&
+        Number(item.provider_id) === Number(providerInfo.value.provider_id) &&
         normalizeDate(item.date) === valFormatted,
     )
     .map((item) => ({
@@ -544,7 +592,7 @@ const onDateSelected = (val) => {
     component: ServiceSelectionSheet,
     componentProps: {
       partialBlocks,
-      provider: props.provider,
+      provider: providerInfo.value,
       selectedDate: val,
       scheduleType: 'default'
     },
@@ -593,7 +641,7 @@ const onDateSelected = (val) => {
         });
 
         if (!servicePackage.provider) {
-          servicePackage.setProvider({ ...props.provider });
+          servicePackage.setProvider({ ...providerInfo.value });
         }
 
         $q.notify({
@@ -612,7 +660,7 @@ const onDateSelected = (val) => {
 
       $q.dialog({
         component: ServicePackageSummaryDialog,
-        componentProps: { initialBooking: booking, provider: props.provider },
+        componentProps: { initialBooking: booking, provider: providerInfo.value },
       }).onOk(() => {
         dialogRef.value.hide();
       });
@@ -624,7 +672,7 @@ const openProviderInfo = () => {
   $q.dialog({
     component: ProfileInfoDialog,
     componentProps: {
-      provider: props.provider,
+      provider: providerInfo.value,
     },
   });
 };
@@ -660,7 +708,7 @@ const toggleFavorite = async () => {
     } else {
       const created = await createClientFavoriteProvider({
         client_id: clientId,
-        provider_id: props.provider.provider_id,
+        provider_id: providerInfo.value.provider_id,
       });
 
       favoriteId.value = created?.id ?? null;
@@ -682,6 +730,7 @@ const toggleFavorite = async () => {
 };
 
 onMounted(() => {
+  loadProviderProfile();
   loadAvailability();
   loadFavoriteState();
   loadReviews();