浏览代码

refactor: payments agora sao por meio de services-packages

Gustavo Mantovani 5 天之前
父节点
当前提交
b7fb2a97fd

+ 5 - 0
src/api/payment.js

@@ -15,6 +15,11 @@ export const getSchedulePixPayment = async (scheduleId) => {
   return data.payload;
 };
 
+export const getServicePackagePix = async (servicePackageId) => {
+  const { data } = await api.get(`/payment/service-package/${servicePackageId}/pix`);
+  return data.payload;
+};
+
 export const getPaymentPlatformFees = async () => {
   const { data } = await api.get('/payment/platform-fees');
   return data.payload;

+ 65 - 18
src/components/dashboard/DashboardPendingSchedules.vue

@@ -17,21 +17,26 @@
                 :style="avatarColors[item.id % avatarColors.length]"
               >
                 <img
-                  v-if="item.provider_photo"
-                  :src="item.provider_photo"
+                  v-if="providerPhoto(item)"
+                  :src="providerPhoto(item)"
                   style="object-fit: cover; border-radius: 50%"
                 />
 
                 <span v-else>
                   {{
-                    item.provider_name?.slice(0, 2).toUpperCase() ?? "??"
+                    providerName(item).slice(0, 2).toUpperCase() || "??"
                   }}
                 </span>
               </q-avatar>
 
               <div class="col column no-wrap overflow-hidden">
                 <span class="font12 fontmedium text-text">
-                  <span v-if="item.status == 'pending'">
+                  <span v-if="type === 'servicePackage'">
+                    {{
+                      $t("dashboard_client.pending_schedules.pay_to_provider")
+                    }}
+                  </span>
+                  <span v-else-if="item.status == 'pending'">
                     {{
                       $t("dashboard_client.pending_schedules.requesting_with")
                     }}
@@ -44,7 +49,7 @@
                   </span>
 
                   <span class="font12 fontbold">
-                    {{ " " + (getFirstName(item.provider_name) || "—") }}</span
+                    {{ " " + (getFirstName(providerName(item)) || "—") }}</span
                   >
                 </span>
 
@@ -57,7 +62,7 @@
                   />
 
                   <span class="font9 fontmedium text-grey-5">
-                    {{ item.time_since_request }}
+                    {{ displayTime(item) }}
                   </span>
                 </div>
               </div>
@@ -77,9 +82,11 @@
                   class="font10 fontmedium text-primary q-mt-xs badge-status-text"
                 >
                   {{
-                    $t(
-                      `dashboard_client.pending_schedules.status.${item.status ?? "pending"}`,
-                    )
+                    type === 'servicePackage'
+                      ? $t("dashboard_client.pending_schedules.status.accepted")
+                      : $t(
+                          `dashboard_client.pending_schedules.status.${item.status ?? "pending"}`,
+                        )
                   }}
                 </span>
               </div>
@@ -94,6 +101,7 @@
 
             <div class="row items-center no-wrap">
               <q-btn
+                v-if="type !== 'servicePackage'"
                 class="q-mr-sm flex-shrink-0"
                 color="primary"
                 dense
@@ -117,13 +125,7 @@
                 />
 
                 {{
-                  [
-                    item.address?.address,
-                    item.address?.number,
-                    item.address?.district,
-                  ]
-                    .filter(Boolean)
-                    .join(", ") || "—"
+                  displayAddress(item)
                 }}
               </span>
             </div>
@@ -135,13 +137,58 @@
 </template>
 
 <script setup>
+import { computed } from "vue";
 import { avatarColors } from "src/helpers/avatarColors";
 import { getFirstName } from "src/helpers/utils";
 
-defineProps({ data: { type: Array, default: () => [] } });
+const props = defineProps({
+  data: { type: Array, default: () => [] },
+  type: { type: String, default: 'schedule' },
+});
 
 const emit = defineEmits(["view-details", "cancel"]);
 
+const isServicePackage = computed(() => props.type === 'servicePackage');
+
+const providerPhoto = (item) => {
+  if (isServicePackage.value) {
+    return item.provider?.profile_media?.url ?? item.provider_photo ?? null;
+  }
+  return item.provider_photo;
+};
+
+const providerName = (item) => {
+  if (isServicePackage.value) {
+    return item.provider?.user?.name ?? item.provider_name ?? '';
+  }
+  return item.provider_name ?? '';
+};
+
+const displayAddress = (item) => {
+  if (isServicePackage.value) {
+    const firstSchedule = item.schedules?.[0];
+    const addr = firstSchedule?.address;
+    if (addr) {
+      return [addr.address, addr.number, addr.district].filter(Boolean).join(', ') || '—';
+    }
+    return '—';
+  }
+  return [
+    item.address?.address,
+    item.address?.number,
+    item.address?.district,
+  ].filter(Boolean).join(', ') || '—';
+};
+
+const displayTime = (item) => {
+  if (isServicePackage.value) {
+    return item.schedules?.length
+      ? `${item.schedules.length} agendamentos`
+      : '—';
+  }
+  return item.time_since_request;
+};
+
 const statusProgressMap = {
   pending: 20,
   accepted: 40,
@@ -153,7 +200,7 @@ const statusProgressMap = {
 const progressPercent = (status) => statusProgressMap[status] ?? 20;
 
 const seeDetails = (item) => {
-  if (item.status === "accepted") {
+  if (isServicePackage.value || item.status === "accepted") {
     emit("view-details", item);
   }
 };

+ 66 - 33
src/components/dashboard/ScheduleAcceptedDialog.vue

@@ -13,15 +13,15 @@
           size="80px"
           :style="avatarStyle"
         >
-          {{ schedule.provider_name?.slice(0, 2).toUpperCase() ?? "??" }}
+          {{ displayProviderName.slice(0, 2).toUpperCase() || "??" }}
         </q-avatar>
 
         <div class="font16 fontbold provider-name">
-          {{ getFirstName(schedule.provider_name) || "—" }}
+          {{ getFirstName(displayProviderName) || "—" }}
         </div>
 
         <div class="font14 fontmedium text-text">
-          {{ schedule.address?.district || "" }}
+          {{ displayDistrict }}
         </div>
       </q-card-section>
 
@@ -40,7 +40,7 @@
           </span>
 
           <span class="detail-value">
-            {{ formattedDate }}
+            {{ displayDate }}
           </span>
         </div>
 
@@ -50,9 +50,9 @@
           </span>
 
           <span class="detail-valued text-text">
-            {{ schedule.start_time?.slice(0, 5) }}
+            {{ displayStartTime }}
             {{ $t("dashboard_client.next_schedules.to") }}
-            {{ schedule.end_time?.slice(0, 5) }}
+            {{ displayEndTime }}
           </span>
         </div>
 
@@ -153,7 +153,8 @@ import SchedulePaymentDialog from "./SchedulePaymentDialog.vue";
 import SchedulePaymentPixDialog from "./SchedulePaymentPixDialog.vue";
 
 const props = defineProps({
-  schedule: { type: Object, required: true },
+  schedule: { type: Object, default: null },
+  servicePackage: { type: Object, default: null },
 });
 
 defineEmits([...useDialogPluginComponent.emits]);
@@ -166,38 +167,66 @@ const paymentStore = usePaymentStore();
 
 const { platformFees, loadPlatformFees } = usePaymentPlatformFees();
 
+const isServicePackage = computed(() => !!props.servicePackage);
+const item = computed(() => props.servicePackage ?? props.schedule);
+
+const displayProviderName = computed(() => {
+  if (isServicePackage.value) {
+    return item.value.provider?.user?.name ?? item.value.provider_name ?? '';
+  }
+  return item.value.provider_name ?? '';
+});
+
+const displayDistrict = computed(() => {
+  if (isServicePackage.value) {
+    return item.value.schedules?.[0]?.address?.district ?? item.value.address?.district ?? '';
+  }
+  return item.value.address?.district ?? '';
+});
+
+const displayDate = computed(() => {
+  const src = isServicePackage.value ? item.value.schedules?.[0] : item.value;
+  if (!src) return '';
+  if (src.formatted_date) return src.formatted_date;
+  const raw = String(src.date || '');
+  const m = raw.match(/^(\d{4})-(\d{2})-(\d{2})/);
+  if (!m) return raw;
+  const d = new Date(+m[1], +m[2] - 1, +m[3]);
+  return d.toLocaleDateString('pt-BR', {
+    day: '2-digit',
+    month: 'long',
+    year: 'numeric',
+  });
+});
+
+const displayStartTime = computed(() => {
+  if (isServicePackage.value) {
+    return item.value.schedules?.[0]?.start_time?.slice(0, 5) ?? '';
+  }
+  return item.value.start_time?.slice(0, 5) ?? '';
+});
+
+const displayEndTime = computed(() => {
+  if (isServicePackage.value) {
+    return item.value.schedules?.[0]?.end_time?.slice(0, 5) ?? '';
+  }
+  return item.value.end_time?.slice(0, 5) ?? '';
+});
+
 const avatarStyle = computed(
-  () => avatarColors[props.schedule.id % avatarColors.length],
+  () => avatarColors[item.value.id % avatarColors.length],
 );
 
-const baseAmount = computed(() => Number(props.schedule.total_amount) || 0);
+const baseAmount = computed(() => Number(item.value.total_amount) || 0);
 
 const creditCardTotal = computed(() =>
   parseFloat((baseAmount.value + platformFee("credit_card")).toFixed(2)),
 );
 
 const hasServicePackageDiscount = computed(() =>
-  scheduleUsesServicePackageDiscount(props.schedule),
+  scheduleUsesServicePackageDiscount(item.value),
 );
 
-const formattedDate = computed(() => {
-  if (props.schedule.formatted_date) return props.schedule.formatted_date;
-
-  const raw = String(props.schedule.date || "");
-
-  const m = raw.match(/^(\d{4})-(\d{2})-(\d{2})/);
-
-  if (!m) return raw;
-
-  const d = new Date(+m[1], +m[2] - 1, +m[3]);
-
-  return d.toLocaleDateString("pt-BR", {
-    day: "2-digit",
-    month: "long",
-    year: "numeric",
-  });
-});
-
 const pixDiscount = computed(() =>
   Math.max(0, parseFloat((creditCardTotal.value - pixTotal.value).toFixed(2))),
 );
@@ -207,7 +236,7 @@ const pixTotal = computed(() =>
 
 const platformFee = (paymentType) => {
   const feeRate = getSchedulePlatformFeeRate(
-    props.schedule,
+    item.value,
     paymentType,
     platformFees.value,
   );
@@ -216,16 +245,20 @@ const platformFee = (paymentType) => {
 };
 
 const onGoToPayment = () => {
-  const hasValidPixPayment = !!paymentStore.getValidPixPayment(
-    props.schedule.id,
-  );
+  const validPixPayment = isServicePackage.value
+    ? paymentStore.getValidPixPaymentForServicePackage(item.value.id)
+    : paymentStore.getValidPixPayment(item.value.id);
+
+  const hasValidPixPayment = !!validPixPayment;
 
   $q.dialog({
     component: hasValidPixPayment
       ? SchedulePaymentPixDialog
       : SchedulePaymentDialog,
     componentProps: {
-      schedule: props.schedule,
+      ...(isServicePackage.value
+        ? { servicePackage: item.value }
+        : { schedule: item.value }),
       ...(hasValidPixPayment ? { total: pixTotal.value } : {}),
     },
   }).onOk(() => {

+ 45 - 15
src/components/dashboard/SchedulePaymentDialog.vue

@@ -3,7 +3,7 @@
     <div class="bg-page full-height column">
 
       <div class="row items-center q-px-md q-pt-md q-pb-sm bg-surface shadow-header">
-        <q-btn icon="mdi-chevron-left" flat round dense color="primary" @click="onDialogCancel" />
+        <q-btn class="header-back-btn" icon="mdi-chevron-left" flat round dense color="primary" @click="onDialogCancel" />
         <q-space />
         <span class="font16 fontbold gradient-diarista">
           {{ $t('payment.title') }}
@@ -122,7 +122,11 @@ import SchedulePaymentProcessingDialog from './SchedulePaymentProcessingDialog.v
 const props = defineProps({
   schedule: {
     type: Object,
-    required: true,
+    default: null,
+  },
+  servicePackage: {
+    type: Object,
+    default: null,
   },
 })
 
@@ -135,26 +139,37 @@ const store = userStore()
 const paymentStore = usePaymentStore()
 const { platformFees, loadPlatformFees } = usePaymentPlatformFees()
 
+const isServicePackage = computed(() => !!props.servicePackage)
+const item = computed(() => props.servicePackage ?? props.schedule)
+
 const selectedMethod = ref(null)
 const agreedToTerms = ref(false)
 const paymentMethods = ref([])
 const loadingCards = ref(false)
 
 const selectedPaymentType = computed(() => selectedMethod.value === 'pix' ? 'pix' : 'credit_card')
-const selectedTotal = computed(() => getScheduleTotalWithPlatformFee(props.schedule, selectedPaymentType.value, platformFees.value))
+const selectedTotal = computed(() => getScheduleTotalWithPlatformFee(item.value, selectedPaymentType.value, platformFees.value))
+
+const getAddress = () => {
+  if (isServicePackage.value) {
+    return item.value.schedules?.[0]?.address ?? item.value.address ?? null;
+  }
+  return item.value.address ?? null;
+};
 
 const addressTypeLabel = computed(() => {
-  const type = props.schedule.address?.address_type
-  if (!type) return ''
-  return t(`profile.address.type.${type}`, type)
-})
+  const addr = getAddress();
+  const type = addr?.address_type;
+  if (!type) return '';
+  return t(`profile.address.type.${type}`, type);
+});
 
 const addressFullText = computed(() => {
-  const a = props.schedule.address
-  if (!a) return ''
-  const parts = [a.address, a.number, a.district].filter(Boolean)
-  return parts.join(', ')
-})
+  const a = getAddress();
+  if (!a) return '';
+  const parts = [a.address, a.number, a.district].filter(Boolean);
+  return parts.join(', ');
+});
 
 const canConfirm = computed(() => selectedMethod.value !== null && agreedToTerms.value)
 
@@ -193,7 +208,12 @@ const openAddCard = () => {
 const openPixPayment = () => {
   $q.dialog({
     component: SchedulePaymentPixDialog,
-    componentProps: { schedule: props.schedule, total: selectedTotal.value },
+    componentProps: {
+      ...(isServicePackage.value
+        ? { servicePackage: item.value }
+        : { schedule: item.value }),
+      total: selectedTotal.value,
+    },
   }).onOk(() => {
     onDialogOK()
   })
@@ -208,7 +228,13 @@ const onConfirm = () => {
 
   $q.dialog({
     component: SchedulePaymentProcessingDialog,
-    componentProps: { schedule: props.schedule, clientPaymentMethodId, total: selectedTotal.value },
+    componentProps: {
+      ...(isServicePackage.value
+        ? { servicePackage: item.value }
+        : { schedule: item.value }),
+      clientPaymentMethodId,
+      total: selectedTotal.value,
+    },
   }).onOk(() => {
     onDialogOK()
   })
@@ -217,7 +243,11 @@ const onConfirm = () => {
 onMounted(() => {
   loadPlatformFees().catch(() => {})
 
-  if (paymentStore.getValidPixPayment(props.schedule.id)) {
+  const existingPix = isServicePackage.value
+    ? paymentStore.getValidPixPaymentForServicePackage(item.value.id)
+    : paymentStore.getValidPixPayment(item.value.id)
+
+  if (existingPix) {
     openPixPayment()
     return
   }

+ 40 - 10
src/components/dashboard/SchedulePaymentPixDialog.vue

@@ -3,7 +3,7 @@
     <div class="bg-page full-height column">
 
       <div class="row items-center q-px-md q-pt-md q-pb-sm bg-surface shadow-header">
-        <q-btn icon="mdi-chevron-left" flat round dense color="primary" @click="onDialogCancel" />
+        <q-btn class="header-back-btn" icon="mdi-chevron-left" flat round dense color="primary" @click="onDialogCancel" />
         <q-space />
         <span class="font16 fontbold gradient-diarista">
           {{ $t('payment.pix_title') }}
@@ -110,11 +110,12 @@
 import { computed, ref, onMounted, onUnmounted } from 'vue'
 import { useDialogPluginComponent, useQuasar, copyToClipboard } from 'quasar'
 import { formatCurrency } from 'src/helpers/utils'
-import { getSchedulePixPayment, paySchedule } from 'src/api/payment'
+import { getSchedulePixPayment, getServicePackagePix, paySchedule, payServicePackage } from 'src/api/payment'
 import { usePaymentStore } from 'src/stores/payment'
 
 const props = defineProps({
-  schedule: { type: Object, required: true },
+  schedule: { type: Object, default: null },
+  servicePackage: { type: Object, default: null },
   total: { type: Number, required: true },
 })
 
@@ -124,6 +125,10 @@ const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginC
 const $q = useQuasar()
 const paymentStore = usePaymentStore()
 
+const isServicePackage = computed(() => !!props.servicePackage)
+const item = computed(() => props.servicePackage ?? props.schedule)
+const itemId = computed(() => item.value.id)
+
 const payment = ref(null)
 const success = ref(false)
 const processing = ref(true)
@@ -168,21 +173,33 @@ const applyPaymentStatus = (nextPayment) => {
   if (nextPayment.status === 'paid') {
     success.value = true
     processing.value = false
-    paymentStore.clearPixPayment(props.schedule.id)
+    if (isServicePackage.value) {
+      paymentStore.clearPixPaymentForServicePackage(itemId.value)
+    } else {
+      paymentStore.clearPixPayment(itemId.value)
+    }
     stopPolling()
     return
   }
 
   if (['failed', 'cancelled'].includes(nextPayment.status)) {
     processing.value = false
-    paymentStore.clearPixPayment(props.schedule.id)
+    if (isServicePackage.value) {
+      paymentStore.clearPixPaymentForServicePackage(itemId.value)
+    } else {
+      paymentStore.clearPixPayment(itemId.value)
+    }
     stopPolling()
     $q.notify({ type: 'negative', message: nextPayment.failure_message || 'Pagamento Pix não confirmado.' })
     onDialogCancel()
     return
   }
 
-  paymentStore.setPixPayment(props.schedule.id, nextPayment)
+  if (isServicePackage.value) {
+    paymentStore.setPixPaymentForServicePackage(itemId.value, nextPayment)
+  } else {
+    paymentStore.setPixPayment(itemId.value, nextPayment)
+  }
 }
 
 const checkPaymentStatus = async () => {
@@ -190,7 +207,10 @@ const checkPaymentStatus = async () => {
 
   pollingInFlight = true
   try {
-    applyPaymentStatus(await getSchedulePixPayment(props.schedule.id))
+    const result = isServicePackage.value
+      ? await getServicePackagePix(itemId.value)
+      : await getSchedulePixPayment(itemId.value)
+    applyPaymentStatus(result)
     updateCountdown()
   } catch (e) {
     console.error('Erro ao verificar pagamento Pix:', e)
@@ -217,7 +237,11 @@ const updateCountdown = () => {
   if (!pixExpiresAt.value && totalSeconds.value > 0) totalSeconds.value--
 
   if (pixExpiresAt.value && totalSeconds.value <= 0) {
-    paymentStore.clearPixPayment(props.schedule.id)
+    if (isServicePackage.value) {
+      paymentStore.clearPixPaymentForServicePackage(itemId.value)
+    } else {
+      paymentStore.clearPixPayment(itemId.value)
+    }
     stopPolling()
   }
 }
@@ -227,7 +251,9 @@ onMounted(async () => {
   countdownTimer = setInterval(updateCountdown, 1000)
 
   try {
-    const cachedPayment = paymentStore.getValidPixPayment(props.schedule.id)
+    const cachedPayment = isServicePackage.value
+      ? paymentStore.getValidPixPaymentForServicePackage(itemId.value)
+      : paymentStore.getValidPixPayment(itemId.value)
 
     if (cachedPayment) {
       applyPaymentStatus(cachedPayment)
@@ -236,7 +262,11 @@ onMounted(async () => {
       return
     }
 
-    applyPaymentStatus(await paySchedule(props.schedule.id, { payment_method: 'pix' }))
+    const paymentResult = isServicePackage.value
+      ? await payServicePackage(itemId.value, { payment_method: 'pix' })
+      : await paySchedule(itemId.value, { payment_method: 'pix' })
+
+    applyPaymentStatus(paymentResult)
     updateCountdown()
     startPolling()
   } catch (e) {

+ 16 - 7
src/components/dashboard/SchedulePaymentProcessingDialog.vue

@@ -44,13 +44,14 @@
 </template>
 
 <script setup>
-import { ref, onMounted } from 'vue'
+import { ref, computed, onMounted } from 'vue'
 import { useDialogPluginComponent, useQuasar } from 'quasar'
 import LogoDiariaSucesso from 'src/assets/diarinho-success-payment.svg';
-import { paySchedule } from 'src/api/payment'
+import { paySchedule, payServicePackage } from 'src/api/payment'
 
 const props = defineProps({
-  schedule: { type: Object, required: true },
+  schedule: { type: Object, default: null },
+  servicePackage: { type: Object, default: null },
   clientPaymentMethodId: { type: Number, required: true },
   total: { type: Number, default: null },
 })
@@ -60,14 +61,22 @@ defineEmits([...useDialogPluginComponent.emits])
 const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginComponent()
 const $q = useQuasar()
 
+const isServicePackage = computed(() => !!props.servicePackage)
+const item = computed(() => props.servicePackage ?? props.schedule)
+
 const success = ref(false)
 
 onMounted(async () => {
   try {
-    const payment = await paySchedule(props.schedule.id, {
-      payment_method: 'credit_card',
-      client_payment_method_id: props.clientPaymentMethodId,
-    })
+    const payment = isServicePackage.value
+      ? await payServicePackage(item.value.id, {
+          payment_method: 'credit_card',
+          client_payment_method_id: props.clientPaymentMethodId,
+        })
+      : await paySchedule(item.value.id, {
+          payment_method: 'credit_card',
+          client_payment_method_id: props.clientPaymentMethodId,
+        })
 
     if (payment.status !== 'paid') {
       throw new Error(payment.failure_message || 'Pagamento não confirmado.')

+ 1 - 1
src/components/profile/ProfileAddressDialog.vue

@@ -2,7 +2,7 @@
   <q-dialog ref="dialogRef" persistent maximized transition-show="slide-left" transition-hide="slide-right">
     <div class="bg-page full-height no-shadow">
       <div class="row items-center q-px-md q-pt-md q-pb-sm bg-white shadow-profile bg-surface">
-        <q-btn v-close-popup icon="mdi-chevron-left" flat round dense color="primary" />
+        <q-btn v-close-popup class="header-back-btn" icon="mdi-chevron-left" flat round dense color="primary" />
         <q-space />
         <span class="text-subtitle1 text-primary font16 fontbold gradient-diarista">{{ $t('profile.address.title') }}</span>
         <q-space />

+ 2 - 1
src/components/profile/ProfileAddressFormDialog.vue

@@ -12,6 +12,7 @@
       >
         <q-btn
           v-close-popup
+          class="header-back-btn"
           color="primary"
           dense
           flat
@@ -607,4 +608,4 @@ onMounted(() => {
 
   setUpdateFormAsOriginal();
 });
-</script>
+</script>

+ 1 - 0
src/components/profile/ProfileFavoritesDialog.vue

@@ -13,6 +13,7 @@
       >
         <q-btn
           v-close-popup
+          class="header-back-btn"
           color="primary"
           dense
           flat

+ 1 - 1
src/components/profile/ProfileHelpDialog.vue

@@ -3,7 +3,7 @@
     <div class="bg-page full-height column no-shadow">
 
       <div class="row items-center q-px-md q-pt-md q-pb-sm bg-white shadow-profile bg-surface">
-        <q-btn v-close-popup icon="mdi-chevron-left" flat round dense color="primary" />
+        <q-btn v-close-popup class="header-back-btn" icon="mdi-chevron-left" flat round dense color="primary" />
         <q-space />
         <span class="text-subtitle1 text-primary font16 fontbold gradient-diarista">{{ $t('profile.help.title') }}</span>
         <q-space />

+ 2 - 1
src/components/profile/ProfilePaymentAddDialog.vue

@@ -9,6 +9,7 @@
     <div class="bg-page full-height column no-shadow">
       <div class="row items-center q-px-md q-pt-md q-pb-sm bg-white shadow-profile bg-surface">
         <q-btn
+          class="header-back-btn"
           color="primary"
           dense
           flat
@@ -556,4 +557,4 @@ const onOKClick = async () => {
   color: #555;
 }
 
-</style>
+</style>

+ 1 - 1
src/components/profile/ProfilePaymentsDialog.vue

@@ -3,7 +3,7 @@
     <div class="bg-page full-height column no-shadow">
 
       <div class="row items-center q-px-md q-pt-md q-pb-sm bg-white shadow-profile bg-surface">
-        <q-btn v-close-popup icon="mdi-chevron-left" flat round dense color="primary" />
+        <q-btn v-close-popup class="header-back-btn" icon="mdi-chevron-left" flat round dense color="primary" />
         <q-space />
         <span class="text-subtitle1 text-primary font16 fontbold gradient-diarista">{{ $t('profile.payments.subtitle') }}</span>
       <q-space />

+ 1 - 0
src/components/profile/ProfilePrivacyDialog.vue

@@ -12,6 +12,7 @@
       >
         <q-btn
           v-close-popup
+          class="header-back-btn"
           color="primary"
           dense
           flat

+ 1 - 1
src/components/profile/SupportRequestDialog.vue

@@ -3,7 +3,7 @@
     <div class="bg-page full-height column no-shadow support-dialog">
 
       <div class="row items-center q-px-md q-pt-md q-pb-sm bg-white shadow-profile bg-surface">
-        <q-btn v-close-popup icon="mdi-chevron-left" flat round dense color="primary" />
+        <q-btn v-close-popup class="header-back-btn" icon="mdi-chevron-left" flat round dense color="primary" />
         <q-space />
         <span class="text-subtitle1 text-primary font16 fontbold gradient-diarista">{{ $t('support_request.title') }}</span>
         <q-space />

+ 7 - 1
src/css/app.scss

@@ -108,6 +108,12 @@ input[type="number"]::-webkit-outer-spin-button {
   }
 }
 
+// Keep the back-arrow glyph aligned with the horizontal content gutter while
+// preserving the full round touch target and the centered header title.
+.header-back-btn {
+  transform: translateX(-16px);
+}
+
 .q-field .q-field__control {
   border: 1.5px solid #e4e4e4 !important;
   border-radius: 4px !important;
@@ -383,4 +389,4 @@ box-shadow: 1px 4px 4px 0px rgba(0,0,0,0.2);
       color: #1E293B !important;
     }
   }
-}
+}

+ 1 - 0
src/pages/agenda/CalendarPage.vue

@@ -4,6 +4,7 @@
       class="calendar-header row items-center q-px-md q-pt-md q-pb-sm bg-white"
     >
       <q-btn
+        class="header-back-btn"
         color="primary"
         dense
         flat

+ 19 - 1
src/pages/dashboard/DashboardPage.vue

@@ -15,7 +15,14 @@
         <DashboardSummaryInfos v-else :data="summaryInfos" />
         <DashboardPaymentIncomplete v-if="!hasPaymentMethods" />
         <DashboardPendingSchedules
-          v-if="pendingSchedules.length > 0"
+          v-if="pendingServicePackages.length > 0"
+          :data="pendingServicePackages"
+          :type="'servicePackage'"
+          @view-details="openServicePackagePaymentDialog"
+          @cancel="cancelSchedule"
+        />
+        <DashboardPendingSchedules
+          v-if="pendingServicePackages.length === 0 && pendingSchedules.length > 0"
           :data="pendingSchedules"
           @view-details="openAcceptedDialog"
           @cancel="cancelSchedule"
@@ -67,6 +74,7 @@ const hasPaymentMethods = ref(true);
 const headerBar = ref({});
 const summaryInfos = ref({});
 const pendingSchedules = ref([]);
+const pendingServicePackages = ref([]);
 const nextSchedules = ref([]);
 const clientProposals = ref([]);
 const customSchedulesNoProposals = ref([]);
@@ -89,6 +97,15 @@ const openAcceptedDialog = (schedule) => {
   });
 };
 
+const openServicePackagePaymentDialog = (servicePackage) => {
+  $q.dialog({
+    component: ScheduleAcceptedDialog,
+    componentProps: { servicePackage }
+  }).onOk(() => {
+    reloadDashboard();
+  });
+};
+
 const reloadDashboard = async (showLoader = true) => {
   if (showLoader) loading.value = true;
   const response = await dadosDashboard();
@@ -96,6 +113,7 @@ const reloadDashboard = async (showLoader = true) => {
     headerBar.value = response.headerBar;
     summaryInfos.value = response.summaryInfos;
     pendingSchedules.value = response.pendingSchedules ?? [];
+    pendingServicePackages.value = response.pendingServicePackages ?? [];
     nextSchedules.value = response.nextSchedules ?? [];
     lastDoneSchedules.value = response.lastDoneSchedules ?? [];
     favoriteProviders.value = response.favoriteProviders ?? [];

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

@@ -2,6 +2,7 @@
   <q-page class="service-package-page bg-page q-pb-xl">
     <div class="row items-center q-px-md q-pt-md q-pb-sm bg-white shadow-service-package">
       <q-btn
+        class="header-back-btn"
         color="primary"
         dense
         flat

+ 1 - 0
src/pages/profile/ProfileEditDialog.vue

@@ -11,6 +11,7 @@
         class="row items-center q-px-md q-pt-md q-pb-sm bg-white shadow-profile"
       >
         <q-btn
+          class="header-back-btn"
           color="primary"
           dense
           flat

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

@@ -2,7 +2,7 @@
 <template>
   <q-page class="sob-medida-page">
     <div class="row items-center q-px-md q-pt-md q-pb-sm bg-white shadow-profile">
-      <q-btn flat round dense icon="mdi-chevron-left" color="primary" @click="router.back()" />
+      <q-btn class="header-back-btn" flat round dense icon="mdi-chevron-left" color="primary" @click="router.back()" />
       <q-space />
       <span class="text-subtitle1 text-primary font16 fontbold gradient-diarista">{{ $t('sob_medida.page_title') }}</span>
       <q-space />
@@ -708,4 +708,4 @@ onMounted(async () => {
     max-width: 460px;
   }
 }
-</style>
+</style>

+ 1 - 0
src/pages/search/SearchPage.vue

@@ -5,6 +5,7 @@
       class="row items-center q-px-md q-pt-md q-pb-sm bg-white shadow-search"
     >
       <q-btn
+        class="header-back-btn"
         color="primary"
         dense
         flat

+ 1 - 0
src/pages/search/components/SchedulingDialog.vue

@@ -11,6 +11,7 @@
       >
         <q-btn
           v-close-popup
+          class="header-back-btn"
           color="primary"
           dense
           flat

+ 1 - 0
src/pages/search/components/ServicePackageSummaryDialog.vue

@@ -11,6 +11,7 @@
       >
         <q-btn
           v-close-popup
+          class="header-back-btn"
           color="primary"
           dense
           flat

+ 33 - 0
src/stores/payment.js

@@ -11,6 +11,7 @@ const isFutureDate = (date) => {
 
 export const usePaymentStore = defineStore('payment', () => {
   const pixPaymentsByScheduleId = ref({});
+  const pixPaymentsByServicePackageId = ref({});
 
   const getValidPixPayment = (scheduleId) => {
     const payment = pixPaymentsByScheduleId.value[scheduleId] ?? null;
@@ -40,10 +41,42 @@ export const usePaymentStore = defineStore('payment', () => {
     pixPaymentsByScheduleId.value = payments;
   };
 
+  const getValidPixPaymentForServicePackage = (servicePackageId) => {
+    const payment = pixPaymentsByServicePackageId.value[servicePackageId] ?? null;
+    if (!payment) return null;
+
+    if (!isFutureDate(getPixExpiresAt(payment))) {
+      clearPixPaymentForServicePackage(servicePackageId);
+      return null;
+    }
+
+    return payment;
+  };
+
+  const setPixPaymentForServicePackage = (servicePackageId, payment) => {
+    if (!servicePackageId || !payment) return;
+    pixPaymentsByServicePackageId.value = {
+      ...pixPaymentsByServicePackageId.value,
+      [servicePackageId]: payment,
+    };
+  };
+
+  const clearPixPaymentForServicePackage = (servicePackageId) => {
+    if (!servicePackageId || !pixPaymentsByServicePackageId.value[servicePackageId]) return;
+
+    const payments = { ...pixPaymentsByServicePackageId.value };
+    delete payments[servicePackageId];
+    pixPaymentsByServicePackageId.value = payments;
+  };
+
   return {
     pixPaymentsByScheduleId,
+    pixPaymentsByServicePackageId,
     getValidPixPayment,
     setPixPayment,
     clearPixPayment,
+    getValidPixPaymentForServicePackage,
+    setPixPaymentForServicePackage,
+    clearPixPaymentForServicePackage,
   };
 });