فهرست منبع

refactor: add genero nos prestadores, add helper para tratar profile media + ajustes visuais layout

Gustavo Mantovani 6 روز پیش
والد
کامیت
0fc3cb9703
37فایلهای تغییر یافته به همراه4219 افزوده شده و 2727 حذف شده
  1. 82 23
      src/components/dashboard/DashboardFavoriteProviders.vue
  2. 49 20
      src/components/dashboard/DashboardLastDoneSchedules.vue
  3. 109 46
      src/components/dashboard/DashboardNextSchedules.vue
  4. 108 43
      src/components/dashboard/DashboardPendingSchedules.vue
  5. 142 62
      src/components/dashboard/DashboardProvidersClose.vue
  6. 176 68
      src/components/dashboard/DashboardTodaySchedules.vue
  7. 133 84
      src/components/dashboard/NextSchedulesDetailsDialog.vue
  8. 117 69
      src/components/dashboard/ScheduleAcceptedDialog.vue
  9. 223 133
      src/components/dashboard/ScheduleRatingDialog.vue
  10. 77 43
      src/components/defaults/DefaultInputDatePicker.vue
  11. 0 29
      src/components/layout/ScheduleCartMenu.vue
  12. 44 31
      src/components/login/LoginStep1Panel.vue
  13. 68 18
      src/components/login/LoginStep3Panel.vue
  14. 339 133
      src/components/profile/ProfileFavoritesDialog.vue
  15. 122 62
      src/components/profile/ProfilePrivacyDialog.vue
  16. 83 78
      src/composables/useInputRules.js
  17. 20 12
      src/i18n/locales/en.json
  18. 20 12
      src/i18n/locales/es.json
  19. 20 12
      src/i18n/locales/pt.json
  20. 54 33
      src/layouts/MainLayout.vue
  21. 122 31
      src/pages/LoginPage.vue
  22. 225 114
      src/pages/agenda/CalendarPage.vue
  23. 126 63
      src/pages/dashboard/components/DashboardClientProposals.vue
  24. 172 68
      src/pages/location/AddressCompletionPage.vue
  25. 44 29
      src/pages/profile/ProfileEditDialog.vue
  26. 281 102
      src/pages/profile/ProfilePage.vue
  27. 0 529
      src/pages/schedule-cart/ScheduleCartPage.vue
  28. 296 125
      src/pages/search/SearchPage.vue
  29. 303 170
      src/pages/search/components/OrderSummaryDialog.vue
  30. 315 199
      src/pages/search/components/SchedulingDialog.vue
  31. 139 88
      src/pages/search/components/SearchFilterDialog.vue
  32. 80 64
      src/pages/search/components/ServiceSelectionSheet.vue
  33. 50 36
      src/pages/search/components/ServiceTimeSelectionDialog.vue
  34. 23 10
      src/pages/search/components/ServiceTypeInfoDialog.vue
  35. 0 21
      src/router/routes/schedule-cart.route.js
  36. 57 34
      src/stores/registrationFlow.js
  37. 0 33
      src/stores/scheduleCart.js

+ 82 - 23
src/components/dashboard/DashboardFavoriteProviders.vue

@@ -1,34 +1,78 @@
 <template>
   <div class="q-mx-md q-mb-md">
-    <div class="dashboard-section-title gradient-diarista q-mb-sm font16 fontbold">{{ $t('dashboard_client.favorites.title') }}</div>
+    <div
+      class="dashboard-section-title gradient-diarista q-mb-sm font16 fontbold"
+    >
+      {{ $t("dashboard_client.favorites.title") }}
+    </div>
 
     <div class="scroll-track">
       <q-card
         v-for="item in data"
         :key="item.provider_id"
-        class="fav-card shadow-card bg-surface card-border"
         :flat="false"
+        class="fav-card shadow-card bg-surface card-border"
       >
         <q-card-section class="q-pa-sm column text-text">
           <div class="row items-start no-wrap">
             <div class="col-3 q-my-auto">
-              <q-avatar :style="avatarColors[item.provider_id % avatarColors.length]" size="46px" class="">
-                <img v-if="item.provider_photo" :src="item.provider_photo" style="object-fit:cover;border-radius:50%;" />
-                <span v-else>{{ item.provider_name?.slice(0,1).toUpperCase() ?? '—' }}</span>
+              <q-avatar
+                class=""
+                size="46px"
+                :style="avatarColors[item.provider_id % avatarColors.length]"
+              >
+                <img
+                  v-if="getProfileMediaUrl(item)"
+                  class="profile-image"
+                  :src="getProfileMediaUrl(item)"
+                />
+
+                <span v-else>
+                  {{ item.provider_name?.slice(0, 1).toUpperCase() ?? "—" }}
+                </span>
               </q-avatar>
             </div>
+
             <div class="col-5 column q-gutter-y-xs q-my-auto">
-              <span class="text-fav-name font12 fontmedium">{{ getFirstName(item.provider_name) || 'Prestador' }}</span>
-              <span class="text-fav-region font9 fontbold  text-grey-7">{{ item.provider_district ?? $t('dashboard_client.last_schedules.no_address') }}</span>
-              <div v-if="item.average_rating != null" class="row items-center q-gutter-x-xs">
-                <q-icon name="mdi-star" color="warning" size="xs" />
-                <span class="text-fav-name font12 fontregular">{{ Number(item.average_rating).toFixed(1) }}</span>
+              <span class="text-fav-name font12 fontmedium">
+                {{ getFirstName(item.provider_name) || "Prestador" }}
+              </span>
+
+              <span
+                v-if="getProviderGenderLabel(item, t)"
+                class="font9 fontmedium text-grey-7"
+              >
+                {{ getProviderGenderLabel(item, t) }}
+              </span>
+
+              <span class="text-fav-region font9 fontbold text-grey-7">
+                {{
+                  item.provider_district ??
+                  $t("dashboard_client.last_schedules.no_address")
+                }}
+              </span>
+
+              <div
+                v-if="item.average_rating != null"
+                class="row items-center q-gutter-x-xs"
+              >
+                <q-icon
+                  color="warning"
+                  name="mdi-star"
+                  size="xs"
+                />
+
+                <span class="text-fav-name font12 fontregular">
+                  {{ Number(item.average_rating).toFixed(1) }}
+                </span>
               </div>
             </div>
+
             <div class="col-4 column q-mt-auto">
               <q-btn
-                rounded color="primary"
+                color="primary"
                 padding="1px 5px"
+                rounded
                 size="sm"
                 :label="$t('dashboard_client.favorites.view_schedule')"
                 @click="goToScheduling(item)"
@@ -42,19 +86,20 @@
 </template>
 
 <script setup>
-import { useQuasar } from 'quasar';
-import SchedulingDialog from 'src/pages/search/components/SchedulingDialog.vue';
-import { getFirstName } from 'src/helpers/utils';
+import { avatarColors } from "src/helpers/avatarColors";
+import { getFirstName } from "src/helpers/utils";
+import { getProfileMediaUrl } from "src/helpers/profileMedia";
+import { getProviderGenderLabel } from "src/helpers/provider";
+import { useI18n } from "vue-i18n";
+import { useQuasar } from "quasar";
+
+import SchedulingDialog from "src/pages/search/components/SchedulingDialog.vue";
 
-defineProps({ data: { type: Array, default: () => [] } });
+defineProps({ data: { default: () => [], type: Array } });
+
+const { t } = useI18n();
 
 const $q = useQuasar();
-const avatarColors = [
-  { background: '#ffd5df', color: '#932e57' },
-  { background: '#d7e8ff', color: '#2158a8' },
-  { background: '#dfd',    color: '#2a7a3b' },
-  { background: '#ffe5cc', color: '#8a4500' },
-];
 
 const goToScheduling = (provider) => {
   $q.dialog({
@@ -73,12 +118,26 @@ const goToScheduling = (provider) => {
   overscroll-behavior-x: contain;
   scroll-snap-type: x proximity;
   padding-bottom: 8px;
-  &::-webkit-scrollbar { display: none; }
-  &::after { content: ''; flex: 0 0 1px; }
+
+  &::-webkit-scrollbar {
+    display: none;
+  }
+
+  &::after {
+    content: "";
+    flex: 0 0 1px;
+  }
 }
+
 .fav-card {
   flex: 0 0 200px;
   scroll-snap-align: start;
   min-width: 70%;
 }
+
+.profile-image {
+  height: 100%;
+  object-fit: cover;
+  width: 100%;
+}
 </style>

+ 49 - 20
src/components/dashboard/DashboardLastDoneSchedules.vue

@@ -1,25 +1,53 @@
 <template>
   <div class="q-mx-md q-mb-md">
-    <div class="dashboard-section-title gradient-diarista q-mb-sm font16 fontbold">{{ $t('dashboard_client.last_schedules.title') }}</div>
+    <div
+      class="dashboard-section-title gradient-diarista q-mb-sm font16 fontbold"
+    >
+      {{ $t("dashboard_client.last_schedules.title") }}
+    </div>
 
     <div class="scroll-track">
       <q-card
         v-for="item in data"
         :key="item.id"
-        class="mini-card shadow-card bg-surface card-border"
         :flat="false"
+        class="mini-card shadow-card bg-surface card-border"
       >
         <q-card-section class="column q-pa-md q-gutter-y-xs text-text">
-          <q-avatar :style="avatarColors[item.provider_id % avatarColors.length]" size="56px" class="q-mx-auto">
-            <img v-if="item.provider_photo" :src="item.provider_photo" style="object-fit:cover;border-radius:50%;" />
-            <span v-else>{{ item.provider_name?.slice(0,1).toUpperCase() ?? '—' }}</span>
+          <q-avatar
+            class="q-mx-auto"
+            size="56px"
+            :style="avatarColors[item.provider_id % avatarColors.length]"
+          >
+            <img
+              v-if="item.provider_photo"
+              :src="item.provider_photo"
+              style="object-fit: cover; border-radius: 50%"
+            />
+
+            <span v-else>{{
+              item.provider_name?.slice(0, 1).toUpperCase() ?? "—"
+            }}</span>
           </q-avatar>
-          <span class="text-done-name font12 fontmedium"> {{ getFirstName(item.provider_name) || 'Prestador' }}</span>
-          <span class="text-done-district font9 fontbold text-grey-7">{{ item.provider_district != null ? item.provider_district : $t('dashboard_client.last_schedules.no_address') }}</span>
+
+          <span class="text-done-name font12 fontmedium">
+            {{ getFirstName(item.provider_name) || "Prestador" }}
+          </span>
+
+          <span class="text-done-district font9 fontbold text-grey-7">
+            {{
+              item.provider_district != null
+                ? item.provider_district
+                : $t("dashboard_client.last_schedules.no_address")
+            }}
+          </span>
+
           <q-btn
-            rounded color="secondary"
-            padding="1px 5px" size="sm"
             class="q-mt-xs full-width"
+            color="secondary"
+            padding="1px 5px"
+            rounded
+            size="sm"
             :label="$t('dashboard_client.last_schedules.reschedule')"
             @click="goToScheduling(item)"
           />
@@ -30,19 +58,15 @@
 </template>
 
 <script setup>
-import { useQuasar } from 'quasar';
-import SchedulingDialog from 'src/pages/search/components/SchedulingDialog.vue';
-import { getFirstName } from 'src/helpers/utils';
+import { avatarColors } from "src/helpers/avatarColors";
+import { getFirstName } from "src/helpers/utils";
+import { useQuasar } from "quasar";
+
+import SchedulingDialog from "src/pages/search/components/SchedulingDialog.vue";
 
 defineProps({ data: { type: Array, default: () => [] } });
 
 const $q = useQuasar();
-const avatarColors = [
-  { background: '#ffd5df', color: '#932e57' },
-  { background: '#d7e8ff', color: '#2158a8' },
-  { background: '#dfd',    color: '#2a7a3b' },
-  { background: '#ffe5cc', color: '#8a4500' },
-];
 
 const goToScheduling = (provider) => {
   $q.dialog({
@@ -61,8 +85,13 @@ const goToScheduling = (provider) => {
   overscroll-behavior-x: contain;
   scroll-snap-type: x proximity;
   padding-bottom: 8px;
-  &::-webkit-scrollbar { display: none; }
-  &::after { content: ''; flex: 0 0 1px; }
+  &::-webkit-scrollbar {
+    display: none;
+  }
+  &::after {
+    content: "";
+    flex: 0 0 1px;
+  }
 }
 .mini-card {
   flex: 0 0 130px;

+ 109 - 46
src/components/dashboard/DashboardNextSchedules.vue

@@ -1,6 +1,10 @@
 <template>
   <div class="q-mx-md q-mb-md">
-    <div class="dashboard-section-title gradient-diarista q-mb-sm font16 fontbold">{{ $t('dashboard_client.next_schedules.title') }}</div>
+    <div
+      class="dashboard-section-title gradient-diarista q-mb-sm font16 fontbold"
+    >
+      {{ $t("dashboard_client.next_schedules.title") }}
+    </div>
 
     <div class="scroll-wrapper">
       <div class="scroll-track">
@@ -10,18 +14,36 @@
           :flat="false"
           class="schedule-card card-border shadow-card bg-surface"
         >
-          <q-card-section class="q-pa-md row col-12 no-wrap schedule-card-section">
+          <q-card-section
+            class="q-pa-md row col-12 no-wrap schedule-card-section"
+          >
             <div class="column text-center schedule-avatar col-4">
               <div class="col-7 column">
-                <q-avatar :style="avatarColors[item.id % avatarColors.length]" class="q-mx-auto">
-                  <img v-if="item.provider_photo" :src="item.provider_photo" style="object-fit:cover;border-radius:50%;" />
-                  <span v-else>{{ item.provider_name?.slice(0,1).toUpperCase() ?? '—' }}</span>
+                <q-avatar
+                  class="q-mx-auto"
+                  :style="avatarColors[item.id % avatarColors.length]"
+                >
+                  <img
+                    v-if="item.provider_photo"
+                    :src="item.provider_photo"
+                    style="object-fit: cover; border-radius: 50%"
+                  />
+
+                  <span v-else>
+                    {{
+                      item.provider_name?.slice(0, 1).toUpperCase() ?? "—"
+                    }}
+                  </span>
                 </q-avatar>
               </div>
 
               <div class="col-5 column justify-end font10 fontbold">
                 <span class="text-pill text-primary customColor">
-                  {{ item.schedule_type === 'custom' ? $t('dashboard_client.next_schedules.tag_custom') : $t('dashboard_client.next_schedules.tag_default') }}
+                  {{
+                    item.schedule_type === "custom"
+                      ? $t("dashboard_client.next_schedules.tag_custom")
+                      : $t("dashboard_client.next_schedules.tag_default")
+                  }}
                 </span>
               </div>
             </div>
@@ -29,8 +51,13 @@
             <div class="col-4 column text-text q-px-lg schedule-main">
               <div class="column schedule-info">
                 <div>
-                  <span class="text-provider-name provider-name-ellipsis font12 fontbold">
-                    {{ getFirstName(item.provider_name) || $t('dashboard_client.next_schedules.no_provider') }}
+                  <span
+                    class="text-provider-name provider-name-ellipsis font12 fontbold"
+                  >
+                    {{
+                      getFirstName(item.provider_name) ||
+                      $t("dashboard_client.next_schedules.no_provider")
+                    }}
                   </span>
                 </div>
 
@@ -41,17 +68,19 @@
                     </span>
 
                     <span class="text-schedule-date-regular">
-                      {{ ', ' + formatDayMonth(item.date) }}
+                      {{ ", " + formatDayMonth(item.date) }}
                     </span>
                   </div>
 
                   <div class="schedule-line q-pt-xs">
                     <span class="text-schedule-date-regular">
-                      {{ $t('dashboard_client.next_schedules.from') }}&nbsp;
+                      {{ $t("dashboard_client.next_schedules.from") }}&nbsp;
                     </span>
 
                     <span class="text-schedule-date-bold fontbold">
-                      {{ item.start_time?.slice(0,5) }} {{ $t('dashboard_client.next_schedules.to') }} {{ item.end_time?.slice(0,5) }}
+                      {{ item.start_time?.slice(0, 5) }}
+                      {{ $t("dashboard_client.next_schedules.to") }}
+                      {{ item.end_time?.slice(0, 5) }}
                     </span>
                   </div>
                 </div>
@@ -59,15 +88,21 @@
 
               <div class="schedule-place">
                 <div class="column justify-end">
-                  <div class="row items-center no-wrap text-pill-place schedule-place-content">
-
+                  <div
+                    class="row items-center no-wrap text-pill-place schedule-place-content"
+                  >
                     <span class="font10 fontbold q-ml-sm">
                       <q-icon
-                        :name="addressIcon(item.address?.address_type ?? item.custom_address_type)"
                         color="primary"
                         size="12px"
+                        :name="addressIcon(item.address?.address_type ?? item.custom_address_type)"
                       />
-                      {{ addressLabel(item.address?.address_type ?? item.custom_address_type) }}
+                      {{
+                        addressLabel(
+                          item.address?.address_type ??
+                            item.custom_address_type,
+                        )
+                      }}
                     </span>
                   </div>
                 </div>
@@ -80,7 +115,9 @@
                   {{ formatScheduleTotal(item) }}
                 </span>
 
-                <span class="text-price-label schedule-price-label font12 fontmedium">
+                <span
+                  class="text-price-label schedule-price-label font12 fontmedium"
+                >
                   {{ formatLabelByPeriodType(item.period_type) }}
                 </span>
               </div>
@@ -89,9 +126,11 @@
                 <q-btn
                   class="full-width"
                   color="primary"
+                  no-caps
                   padding="1px 5px"
+                  rounded
                   size="sm"
-                  unelevated rounded no-caps
+                  unelevated
                   :label="$t('dashboard_client.next_schedules.details')"
                   @click="emit('view-details', item)"
                 />
@@ -105,56 +144,73 @@
 </template>
 
 <script setup>
-import { formatCurrency,getFirstName,formatLabelByPeriodType } from 'src/helpers/utils';
-import { getSchedulePaymentType, getScheduleTotalWithPlatformFee } from 'src/helpers/paymentPlatformFees';
-import { usePaymentPlatformFees } from 'src/composables/usePaymentPlatformFees';
-import { onMounted } from 'vue';
-import { useI18n } from 'vue-i18n';
+import { avatarColors } from "src/helpers/avatarColors";
+
+import {
+  formatCurrency,
+  formatLabelByPeriodType,
+  getFirstName,
+} from "src/helpers/utils";
+
+import {
+  getSchedulePaymentType,
+  getScheduleTotalWithPlatformFee,
+} from "src/helpers/paymentPlatformFees";
+
+import { onMounted } from "vue";
+import { useI18n } from "vue-i18n";
+import { usePaymentPlatformFees } from "src/composables/usePaymentPlatformFees";
 
 defineProps({ data: { type: Array, default: () => [] } });
 
-const emit = defineEmits(['view-details']);
+const emit = defineEmits(["view-details"]);
 
 const { t } = useI18n();
 const { platformFees, loadPlatformFees } = usePaymentPlatformFees();
 
-const avatarColors = [
-  { background: '#ffd5df', color: '#932e57' },
-  { background: '#d7e8ff', color: '#2158a8' },
-  { background: '#dfd',    color: '#2a7a3b' },
-  { background: '#ffe5cc', color: '#8a4500' },
-];
-
-const addressIcon = (type) => type === 'home' ? 'mdi-home-outline' : 'mdi-office-building-outline';
+const addressIcon = (type) =>
+  type === "home" ? "mdi-home-outline" : "mdi-office-building-outline";
 
 const addressLabel = (type) => {
-  if (type === 'home')       return t('address.types.commercial.home');
-  if (type === 'apartment')  return t('dashboard_client.next_schedules.place_apartment');
-  if (type === 'commercial') return t('address.types.commercial.commercial');
+  if (type === "home") return t("address.types.commercial.home");
+
+  if (type === "apartment")
+    return t("dashboard_client.next_schedules.place_apartment");
 
-  return t('dashboard_client.next_schedules.place_unknown');
+  if (type === "commercial") return t("address.types.commercial.commercial");
+
+  return t("dashboard_client.next_schedules.place_unknown");
 };
 
 const formatScheduleTotal = (item) => {
-  if (!item.total_amount || item.total_amount === '0.00') {
-    return t('dashboard_client.next_schedules.to_combine');
+  if (!item.total_amount || item.total_amount === "0.00") {
+    return t("dashboard_client.next_schedules.to_combine");
   }
 
-  return formatCurrency(getScheduleTotalWithPlatformFee(item, getSchedulePaymentType(item), platformFees.value));
+  return formatCurrency(
+    getScheduleTotalWithPlatformFee(
+      item,
+      getSchedulePaymentType(item),
+      platformFees.value,
+    ),
+  );
 };
 
 const formatDayMonth = (iso) => {
-  if (!iso) return '';
+  if (!iso) return "";
 
-  return new Date(iso).toLocaleDateString('pt-BR', { day: '2-digit', month: '2-digit' });
+  return new Date(iso).toLocaleDateString("pt-BR", {
+    day: "2-digit",
+    month: "2-digit",
+  });
 };
 
 const formatWeekday = (iso) => {
-  if (!iso) return '';
+  if (!iso) return "";
 
   const d = new Date(iso);
 
-  const w = d.toLocaleDateString('pt-BR', { weekday: 'long' });
+  const w = d.toLocaleDateString("pt-BR", { weekday: "long" });
 
   return w.charAt(0).toUpperCase() + w.slice(1);
 };
@@ -165,7 +221,9 @@ onMounted(() => {
 </script>
 
 <style scoped lang="scss">
-.scroll-wrapper { overflow: hidden; }
+.scroll-wrapper {
+  overflow: hidden;
+}
 
 .scroll-track {
   display: flex;
@@ -175,8 +233,13 @@ onMounted(() => {
   overscroll-behavior-x: contain;
   scroll-snap-type: x proximity;
   padding-bottom: 8px;
-  &::-webkit-scrollbar { display: none; }
-  &::after { content: ''; flex: 0 0 1px; }
+  &::-webkit-scrollbar {
+    display: none;
+  }
+  &::after {
+    content: "";
+    flex: 0 0 1px;
+  }
 }
 
 .schedule-card {
@@ -269,7 +332,7 @@ onMounted(() => {
 }
 
 .customColor {
-  background: linear-gradient(180deg, #8B5CF6 50%, #EC4899 50%);
+  background: linear-gradient(180deg, #8b5cf6 50%, #ec4899 50%);
   background-clip: text;
   -webkit-background-clip: text;
   -webkit-text-fill-color: transparent;

+ 108 - 43
src/components/dashboard/DashboardPendingSchedules.vue

@@ -5,62 +5,126 @@
         <q-card
           v-for="item in data"
           :key="item.id"
-          class="pending-card card-border shadow-card bg-surface"
           :flat="false"
+          class="pending-card card-border shadow-card bg-surface"
           @click="seeDetails(item)"
         >
           <q-card-section class="q-pa-md">
-
             <div class="row no-wrap items-start q-mb-sm">
-              <q-avatar size="40px" :style="avatarColors[item.id % avatarColors.length]" class="q-mr-sm flex-shrink-0">
-                <img v-if="item.provider_photo" :src="item.provider_photo" style="object-fit:cover;border-radius:50%;" />
-                <span v-else>{{ item.provider_name?.slice(0, 2).toUpperCase() ?? '??' }}</span>
+              <q-avatar
+                class="q-mr-sm flex-shrink-0"
+                size="40px"
+                :style="avatarColors[item.id % avatarColors.length]"
+              >
+                <img
+                  v-if="item.provider_photo"
+                  :src="item.provider_photo"
+                  style="object-fit: cover; border-radius: 50%"
+                />
+
+                <span v-else>
+                  {{
+                    item.provider_name?.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'">
-                    {{ $t('dashboard_client.pending_schedules.requesting_with') }}
+                    {{
+                      $t("dashboard_client.pending_schedules.requesting_with")
+                    }}
                   </span>
+
                   <span v-else-if="item.status == 'accepted'">
-                    {{ $t('dashboard_client.pending_schedules.pay_to_provider') }}
+                    {{
+                      $t("dashboard_client.pending_schedules.pay_to_provider")
+                    }}
                   </span>
-                  
-                  <span class="font12 fontbold"> {{ ' ' + (getFirstName(item.provider_name) || '—') }}</span>
+
+                  <span class="font12 fontbold">
+                    {{ " " + (getFirstName(item.provider_name) || "—") }}</span
+                  >
                 </span>
+
                 <div class="row items-center q-mt-xs">
-                  <q-icon name="mdi-clock-outline" size="13px" color="grey-5" class="q-mr-xs" />
-                  <span class="font9 fontmedium text-grey-5">{{ item.time_since_request }}</span>
+                  <q-icon
+                    class="q-mr-xs"
+                    color="grey-5"
+                    name="mdi-clock-outline"
+                    size="13px"
+                  />
+
+                  <span class="font9 fontmedium text-grey-5">
+                    {{ item.time_since_request }}
+                  </span>
                 </div>
               </div>
 
-              <div class="clock-badge-col q-ml-sm flex-shrink-0 column items-center">
+              <div
+                class="clock-badge-col q-ml-sm flex-shrink-0 column items-center"
+              >
                 <div class="clock-badge">
-                  <q-icon name="mdi-clock-outline" size="14px" color="white" />
+                  <q-icon
+                    color="white"
+                    name="mdi-clock-outline"
+                    size="14px"
+                  />
                 </div>
-                <span class="font10 fontmedium text-primary q-mt-xs badge-status-text">
-                  {{ $t(`dashboard_client.pending_schedules.status.${item.status ?? 'pending'}`) }}
+
+                <span
+                  class="font10 fontmedium text-primary q-mt-xs badge-status-text"
+                >
+                  {{
+                    $t(
+                      `dashboard_client.pending_schedules.status.${item.status ?? "pending"}`,
+                    )
+                  }}
                 </span>
               </div>
             </div>
 
             <div class="progress-track q-mb-md">
-              <div class="progress-fill" :style="{ width: progressPercent(item.status) + '%' }" />
+              <div
+                class="progress-fill"
+                :style="{ width: progressPercent(item.status) + '%' }"
+              />
             </div>
 
             <div class="row items-center no-wrap">
               <q-btn
-                flat no-caps dense
-                :label="$t('dashboard_client.pending_schedules.cancel_btn')"
+                class="q-mr-sm flex-shrink-0"
                 color="primary"
+                dense
+                flat
+                no-caps
                 size="xs"
-                class="q-mr-sm flex-shrink-0"
+                :label="$t('dashboard_client.pending_schedules.cancel_btn')"
                 @click="$emit('cancel', item)"
               />
+
               <q-space />
-              <span class="font9 fontregular text-grey-6 col ellipsis text-right">
-                <q-icon name="mdi-map-marker-outline" size="13px" color="grey-6" class="q-mr-xs flex-shrink-0" />
-                {{ [item.address?.address, item.address?.number, item.address?.district].filter(Boolean).join(', ') || '—' }}
+
+              <span
+                class="font9 fontregular text-grey-6 col ellipsis text-right"
+              >
+                <q-icon
+                  class="q-mr-xs flex-shrink-0"
+                  color="grey-6"
+                  name="mdi-map-marker-outline"
+                  size="13px"
+                />
+
+                {{
+                  [
+                    item.address?.address,
+                    item.address?.number,
+                    item.address?.district,
+                  ]
+                    .filter(Boolean)
+                    .join(", ") || "—"
+                }}
               </span>
             </div>
           </q-card-section>
@@ -71,38 +135,34 @@
 </template>
 
 <script setup>
-import {getFirstName} from 'src/helpers/utils';
+import { avatarColors } from "src/helpers/avatarColors";
+import { getFirstName } from "src/helpers/utils";
 
+defineProps({ data: { type: Array, default: () => [] } });
 
-const avatarColors = [
-  { background: '#ffd5df', color: '#932e57' },
-  { background: '#d7e8ff', color: '#2158a8' },
-  { background: '#dfd',    color: '#2a7a3b' },
-  { background: '#ffe5cc', color: '#8a4500' },
-];
+const emit = defineEmits(["view-details", "cancel"]);
 
 const statusProgressMap = {
-  pending:  20,
+  pending: 20,
   accepted: 40,
-  paid:     60,
-  started:  80,
+  paid: 60,
+  started: 80,
   finished: 100,
 };
 
 const progressPercent = (status) => statusProgressMap[status] ?? 20;
 
-defineProps({ data: { type: Array, default: () => [] } });
-const emit = defineEmits(['view-details', 'cancel']);
-
 const seeDetails = (item) => {
-  if (item.status === 'accepted') {
-    emit('view-details', item);
+  if (item.status === "accepted") {
+    emit("view-details", item);
   }
 };
 </script>
 
 <style scoped lang="scss">
-.scroll-wrapper { overflow: hidden; }
+.scroll-wrapper {
+  overflow: hidden;
+}
 .scroll-track {
   display: flex;
   flex-direction: row;
@@ -111,8 +171,13 @@ const seeDetails = (item) => {
   overscroll-behavior-x: contain;
   scroll-snap-type: x proximity;
   padding-bottom: 8px;
-  &::-webkit-scrollbar { display: none; }
-  &::after { content: ''; flex: 0 0 1px; }
+  &::-webkit-scrollbar {
+    display: none;
+  }
+  &::after {
+    content: "";
+    flex: 0 0 1px;
+  }
 }
 .pending-card {
   min-width: 80%;
@@ -128,7 +193,7 @@ const seeDetails = (item) => {
   width: 24px;
   height: 24px;
   border-radius: 50%;
-  background: linear-gradient(135deg, #8B5CF6, #EC4899);
+  background: linear-gradient(135deg, #8b5cf6, #ec4899);
   display: flex;
   align-items: center;
   justify-content: center;
@@ -141,7 +206,7 @@ const seeDetails = (item) => {
 .progress-track {
   width: 100%;
   height: 5px;
-  background: #E2E8F0;
+  background: #e2e8f0;
   border-radius: 3px;
   overflow: hidden;
 }
@@ -149,7 +214,7 @@ const seeDetails = (item) => {
 .progress-fill {
   height: 100%;
   border-radius: 3px;
-  background: linear-gradient(90deg, #8B5CF6, #EC4899);
+  background: linear-gradient(90deg, #8b5cf6, #ec4899);
   transition: width 0.4s ease;
 }
 </style>

+ 142 - 62
src/components/dashboard/DashboardProvidersClose.vue

@@ -1,11 +1,30 @@
 <template>
   <div class="q-mx-md q-mb-lg">
     <div class="row items-center justify-between no-wrap q-mb-sm">
-      <div class="dashboard-section-title gradient-diarista font16 fontbold">{{ $t('dashboard_client.providers_close.title') }}</div>
+      <div class="dashboard-section-title gradient-diarista font16 fontbold">
+        {{ $t("dashboard_client.providers_close.title") }}
+      </div>
+
       <div class="text-text-light">
-        <q-btn flat color="text-light" size="sm" icon="mdi-chevron-left" @click="setPeriodTypePrevious"/>
-        <span class="font10 fontbold">{{ showCorrectLabels() }}</span>
-        <q-btn flat color="text-light" size="sm" icon="mdi-chevron-right" @click="setPeriodTypeNext"/>
+        <q-btn
+          color="text-light"
+          flat
+          icon="mdi-chevron-left"
+          size="sm"
+          @click="setPeriodTypePrevious"
+        />
+
+        <span class="font10 fontbold">
+          {{ showCorrectLabels() }}
+        </span>
+
+        <q-btn
+          color="text-light"
+          flat
+          icon="mdi-chevron-right"
+          size="sm"
+          @click="setPeriodTypeNext"
+        />
       </div>
     </div>
 
@@ -13,50 +32,106 @@
       <q-card
         v-for="p in data"
         :key="p.provider_id"
-        class="card-border bg-page text-text q-mb-sm"
         :flat="false"
+        class="card-border bg-page text-text q-mb-sm"
       >
         <q-card-section class="row no-wrap q-pa-sm">
           <div class="row no-wrap full-width">
             <div class="col-2">
-              <q-avatar :style="avatarColors[p.provider_id % avatarColors.length]" class="">
-                <img v-if="p.provider_photo" :src="p.provider_photo" style="object-fit:cover;border-radius:50%;" />
-                <span v-else>{{ p.provider_name?.slice(0,1).toUpperCase() ?? '—' }}</span>
+              <q-avatar
+                class=""
+                :style="avatarColors[p.provider_id % avatarColors.length]"
+              >
+                <img
+                  v-if="p.provider_photo"
+                  :src="p.provider_photo"
+                  style="object-fit: cover; border-radius: 50%"
+                />
+
+                <span v-else>
+                  {{ p.provider_name?.slice(0, 1).toUpperCase() ?? "—" }}
+                </span>
               </q-avatar>
             </div>
 
             <div class="col-10 row">
               <div class="column col-9 justify-between">
-                <span class="text-provider-close-name font12 fontmedium">{{ getFirstName(p.provider_name) }}</span>
-                <span class="text-provider-close-region font9 fontmedium">{{ p.district }}</span>
+                <span class="text-provider-close-name font12 fontmedium">
+                  {{ getFirstName(p.provider_name) }}
+                </span>
+
+                <span
+                  v-if="getProviderGenderLabel(p, t)"
+                  class="font9 fontmedium text-grey-7"
+                >
+                  {{ getProviderGenderLabel(p, t) }}
+                </span>
+
+                <span class="text-provider-close-region font9 fontmedium">
+                  {{ p.district }}
+                </span>
+
                 <div class="row items-center justify-between q-pr-lg">
                   <div class="row items-center">
-                    <q-icon name="mdi-star" color="warning" size="12px" />
+                    <q-icon
+                      color="warning"
+                      name="mdi-star"
+                      size="12px"
+                    />
+
                     <span class="text-provider-close-rating font9 fontmedium">
-                      {{ p.average_rating != null ? (Number(p.average_rating).toFixed(1) + ' (' + (p.total_reviews ?? 0) + ')') : ('(' + (p.total_reviews ?? 0) + ')') }}
+                      {{
+                        p.average_rating != null
+                          ? Number(p.average_rating).toFixed(1) +
+                            " (" +
+                            (p.total_reviews ?? 0) +
+                            ")"
+                          : "(" + (p.total_reviews ?? 0) + ")"
+                      }}
                     </span>
                   </div>
+
                   <div class="row items-center">
-                    <q-icon name="mdi-broom" color="secondary" size="16px" />
-                    <span class="text-provider-close-jobs font9 fontmedium">{{ p.total_services ?? 0 }}</span>
+                    <q-icon
+                      color="secondary"
+                      name="mdi-broom"
+                      size="16px"
+                    />
+
+                    <span class="text-provider-close-jobs font9 fontmedium">
+                      {{ p.total_services ?? 0 }}
+                    </span>
                   </div>
+
                   <div class="row items-center">
-                    <q-icon name="mdi-map-marker-outline" color="text" size="16px" />
-                    <span class="text-provider-close-jobs font9 fontmedium">{{ formatDistance(p.distance_km) }}</span>
+                    <q-icon
+                      color="text"
+                      name="mdi-map-marker-outline"
+                      size="16px"
+                    />
+
+                    <span class="text-provider-close-jobs font9 fontmedium">
+                      {{ formatDistance(p.distance_km) }}
+                    </span>
                   </div>
                 </div>
               </div>
 
-              <div class="column col-3 justify-between text-center items-center">
+              <div
+                class="column col-3 justify-between text-center items-center"
+              >
                 <span class="text-provider-close-price font12 fontmedium">
                   {{ showCorrectValues(p) }}
                 </span>
+
                 <div class="full-width">
                   <q-btn
-                    unelevated rounded no-caps
                     color="primary"
-                    size="sm"
+                    no-caps
                     padding="3px 12px"
+                    rounded
+                    size="sm"
+                    unelevated
                     :label="$t('dashboard_client.providers_close.schedule')"
                     @click="goToScheduling(p)"
                   />
@@ -71,77 +146,83 @@
 </template>
 
 <script setup>
-import { useQuasar } from 'quasar';
-import { formatCurrency,getFirstName } from 'src/helpers/utils';
-import SchedulingDialog from 'src/pages/search/components/SchedulingDialog.vue';
-import { ref } from 'vue';
-import { useI18n } from 'vue-i18n';
+import { avatarColors } from "src/helpers/avatarColors";
+import { formatCurrency, getFirstName } from "src/helpers/utils";
+import { getProviderGenderLabel } from "src/helpers/provider";
+import { ref } from "vue";
+import { useI18n } from "vue-i18n";
+import { useQuasar } from "quasar";
+
+import SchedulingDialog from "src/pages/search/components/SchedulingDialog.vue";
 
 defineProps({ data: { type: Array, default: () => [] } });
 
-const $q = useQuasar();
 const { t } = useI18n();
+
+const $q = useQuasar();
+
 const currentPeriodType = ref(8);
+
 const periodTypeMap = ref({
-  2: 'daily_price_2h',
-  4: 'daily_price_4h',
-  6: 'daily_price_6h',
-  8: 'daily_price_8h',
+  2: "daily_price_2h",
+  4: "daily_price_4h",
+  6: "daily_price_6h",
+  8: "daily_price_8h",
 });
 
 const formatDistance = (distance) => {
-  if (distance === null || distance === undefined || distance === '') return '—';
+  if (distance === null || distance === undefined || distance === "")
+    return "—";
 
   const numericDistance = Number(distance);
-  if (!Number.isFinite(numericDistance)) return '—';
 
-  return `${numericDistance.toLocaleString('pt-BR', { maximumFractionDigits: 1 })} km`;
+  if (!Number.isFinite(numericDistance)) return "—";
+
+  return `${numericDistance.toLocaleString("pt-BR", { maximumFractionDigits: 1 })} km`;
 };
 
-const showCorrectValues = (p) => {
-  const priceKey = periodTypeMap.value[currentPeriodType.value];
-  const price = priceKey ? p[priceKey] : null;
+const setPeriodTypePrevious = () => {
+  const previousPeriod = currentPeriodType.value - 2;
 
-  return price !== null && price !== undefined
-    ? formatCurrency(price)
-    : t('dashboard_client.providers_close.no_price');
+  if (periodTypeMap.value[previousPeriod]) {
+    currentPeriodType.value = previousPeriod;
+  }
+};
+
+const setPeriodTypeNext = () => {
+  const nextPeriod = currentPeriodType.value + 2;
+
+  if (periodTypeMap.value[nextPeriod]) {
+    currentPeriodType.value = nextPeriod;
+  }
 };
 
 const showCorrectLabels = () => {
   switch (currentPeriodType.value) {
     case 8:
-      return t('dashboard_client.providers_close.until_8h');
+      return t("dashboard_client.providers_close.until_8h");
     case 6:
-      return t('dashboard_client.providers_close.until_6h');
+      return t("dashboard_client.providers_close.until_6h");
     case 4:
-      return t('dashboard_client.providers_close.until_4h');
+      return t("dashboard_client.providers_close.until_4h");
     case 2:
-      return t('dashboard_client.providers_close.until_2h');
+      return t("dashboard_client.providers_close.until_2h");
     default:
-      return '';
+      return "";
   }
 };
 
-const setPeriodTypePrevious = () => {
-  const previousPeriod = currentPeriodType.value - 2;
-  if (periodTypeMap.value[previousPeriod]) {
-    currentPeriodType.value = previousPeriod;
-  }
-};
+const showCorrectValues = (p) => {
+  const priceKey = periodTypeMap.value[currentPeriodType.value];
 
-const setPeriodTypeNext = () => {
-  const nextPeriod = currentPeriodType.value + 2;
-  if (periodTypeMap.value[nextPeriod]) {
-    currentPeriodType.value = nextPeriod;
-  }
+  const price = priceKey ? p[priceKey] : null;
+
+  return price !== null && price !== undefined
+    ? formatCurrency(price)
+    : t("dashboard_client.providers_close.no_price");
 };
 
-const avatarColors = [
-  { background: '#ffd5df', color: '#932e57' },
-  { background: '#d7e8ff', color: '#2158a8' },
-  { background: '#dfd',    color: '#2a7a3b' },
-  { background: '#ffe5cc', color: '#8a4500' },
-];
+//
 
 const goToScheduling = (provider) => {
   $q.dialog({
@@ -151,5 +232,4 @@ const goToScheduling = (provider) => {
 };
 </script>
 
-<style scoped lang="scss">
-</style>
+<style scoped lang="scss"></style>

+ 176 - 68
src/components/dashboard/DashboardTodaySchedules.vue

@@ -5,96 +5,194 @@
         <q-card
           v-for="item in data"
           :key="item.id"
-          class="today-card card-border shadow-card bg-surface"
           :flat="false"
+          class="today-card card-border shadow-card bg-surface"
         >
           <q-card-section class="q-pa-md">
             <div class="row no-wrap items-start q-mb-xs">
-              <q-avatar size="40px" class="flex-shrink-0 q-mr-sm">
-                <img v-if="item.provider_photo" :src="item.provider_photo" />
-                <span v-else :style="avatarColors[item.id % avatarColors.length]" class="full-width full-height flex flex-center" style="border-radius:50%;">
-                  {{ item.provider_name?.slice(0, 2).toUpperCase() ?? '??' }}
+              <q-avatar
+                class="flex-shrink-0 q-mr-sm"
+                size="40px"
+              >
+                <img
+                  v-if="item.provider_photo"
+                  :src="item.provider_photo"
+                />
+
+                <span
+                  v-else
+                  :style="avatarColors[item.id % avatarColors.length]"
+                  class="full-width full-height flex flex-center"
+                  style="border-radius: 50%"
+                >
+                  {{ item.provider_name?.slice(0, 2).toUpperCase() ?? "??" }}
                 </span>
               </q-avatar>
+
               <div class="col-7 column no-wrap overflow-hidden justify-center">
                 <span class="font12 fontmedium text-text leading-tight">
-                  <template v-if="cardState(item) === 'awaiting_code'">{{ $t('dashboard_client.today_schedules.start_with') }}</template>
-                  <template v-else-if="cardState(item) === 'in_progress'">{{ $t('dashboard_client.today_schedules.started_by') }}</template>
-                  <template v-else>{{ $t('dashboard_client.today_schedules.finished_by') }}</template>
-                  <span class="fontbold">  {{ ' ' + (getFirstName(item.provider_name) || '—') }}</span>
+                  <template v-if="cardState(item) === 'awaiting_code'">
+                    {{ $t("dashboard_client.today_schedules.start_with") }}
+                  </template>
+
+                  <template v-else-if="cardState(item) === 'in_progress'">
+                    {{ $t("dashboard_client.today_schedules.started_by") }}
+                  </template>
+
+                  <template v-else>
+                    {{ $t("dashboard_client.today_schedules.finished_by") }}
+                  </template>
+
+                  <span class="fontbold">
+                    {{ " " + (getFirstName(item.provider_name) || "—") }}
+                  </span>
                 </span>
+
                 <div class="row items-center q-mt-xs no-wrap">
-                  <q-icon name="mdi-clock-outline" size="13px" class="q-mr-xs gradient-diarista" />
+                  <q-icon
+                    class="q-mr-xs gradient-diarista"
+                    name="mdi-clock-outline"
+                    size="13px"
+                  />
+
                   <span class="font9 fontregular text-grey-6">
-                    {{ $t('dashboard_client.next_schedules.from') }} {{ item.start_time?.slice(0, 5) }} {{ $t('dashboard_client.next_schedules.to') }} {{ item.end_time?.slice(0, 5) }}
+                    {{ $t("dashboard_client.next_schedules.from") }}
+                    {{ item.start_time?.slice(0, 5) }}
+                    {{ $t("dashboard_client.next_schedules.to") }}
+                    {{ item.end_time?.slice(0, 5) }}
                   </span>
                 </div>
               </div>
-              <div class="flex-shrink-0 q-ml-sm column items-center justify-start">
+
+              <div
+                class="flex-shrink-0 q-ml-sm column items-center justify-start"
+              >
                 <template v-if="cardState(item) === 'awaiting_code'">
                   <div class="column items-center">
-                    <span class="font9 fontbold text-primary q-mb-xs">{{ $t('dashboard_client.today_schedules.code_label') }}</span>
-                    <div class="code-pill fontbold font10 bg-primary">{{ item.code }}</div>
+                    <span class="font9 fontbold text-primary q-mb-xs">
+                      {{ $t("dashboard_client.today_schedules.code_label") }}
+                    </span>
+
+                    <div class="code-pill fontbold font10 bg-primary">
+                      {{ item.code }}
+                    </div>
                   </div>
                 </template>
 
                 <template v-else-if="cardState(item) === 'in_progress'">
                   <div class="column items-center">
                     <div class="clock-badge">
-                      <q-icon name="mdi-clock-outline" size="15px" color="white" />
+                      <q-icon
+                        color="white"
+                        name="mdi-clock-outline"
+                        size="15px"
+                      />
                     </div>
-                    <span class="font10 fontregular badge-status-text text-primary q-mt-xs">
-                      {{ $t('dashboard_client.today_schedules.in_progress') }}
+
+                    <span
+                      class="font10 fontregular badge-status-text text-primary q-mt-xs"
+                    >
+                      {{ $t("dashboard_client.today_schedules.in_progress") }}
                     </span>
                   </div>
                 </template>
 
                 <template v-else>
-                  <div v-if="item.client_reviewed" class="rate-btn reviewed-badge">
-                    <q-icon name="mdi-star" size="12px" class="q-mr-xs" />
-                    {{ $t('dashboard_client.schedule_rating.reviewed_badge') }}
+                  <div
+                    v-if="item.client_reviewed"
+                    class="rate-btn reviewed-badge"
+                  >
+                    <q-icon
+                      class="q-mr-xs"
+                      name="mdi-star"
+                      size="12px"
+                    />
+
+                    {{ $t("dashboard_client.schedule_rating.reviewed_badge") }}
                   </div>
+
                   <q-btn
                     v-else
-                    unelevated no-caps
                     class="rate-btn"
+                    no-caps
+                    unelevated
                     @click.stop="emit('rate', item)"
                   >
                     <div class="column items-center">
-                      <q-icon name="mdi-star-outline" size="12px" class="q-mr-xs" />
-                      {{ $t('dashboard_client.today_schedules.rate_btn') }}
+                      <q-icon
+                        class="q-mr-xs"
+                        name="mdi-star-outline"
+                        size="12px"
+                      />
+
+                      {{ $t("dashboard_client.today_schedules.rate_btn") }}
                     </div>
                   </q-btn>
                 </template>
               </div>
             </div>
 
-
             <div class="progress-track q-mb-sm">
-              <div class="progress-fill" :class="cardState(item) === 'finished' ? 'progress-fill--finished' : ''" :style="{ width: progressByState(item) + '%' }" />
+              <div
+                class="progress-fill"
+                :class="cardState(item) === 'finished' ? 'progress-fill--finished' : ''"
+                :style="{ width: progressByState(item) + '%' }"
+              />
             </div>
+
             <div class="row items-center no-wrap">
               <template v-if="cardState(item) !== 'finished'">
                 <q-btn
-                  flat no-caps dense
-                  :label="$t('dashboard_client.today_schedules.help_btn')"
+                  class="flex-shrink-0"
                   color="primary"
+                  dense
+                  flat
+                  no-caps
                   size="xs"
-                  class="flex-shrink-0"
+                  :label="$t('dashboard_client.today_schedules.help_btn')"
                   @click.stop="openHelp"
                 />
+
                 <q-space />
+
                 <template v-if="cardState(item) === 'awaiting_code'">
-                  <span class="font9 fontregular text-grey-6 col ellipsis text-right">
-                    <q-icon name="mdi-map-marker-outline" size="13px" color="grey-6" class="q-mr-xs flex-shrink-0" />
-                    {{ [item.address?.address, item.address?.number, item.address?.district].filter(Boolean).join(', ') || '—' }}
+                  <span
+                    class="font9 fontregular text-grey-6 col ellipsis text-right"
+                  >
+                    <q-icon
+                      class="q-mr-xs flex-shrink-0"
+                      color="grey-6"
+                      name="mdi-map-marker-outline"
+                      size="13px"
+                    />
+
+                    {{
+                      [
+                        item.address?.address,
+                        item.address?.number,
+                        item.address?.district,
+                      ]
+                        .filter(Boolean)
+                        .join(", ") || "—"
+                    }}
                   </span>
                 </template>
+
                 <template v-else>
-                  <q-icon name="mdi-clock-outline" size="13px" class="q-mr-xs flex-shrink-0 gradient-diarista" />
-                  <span class="font9 fontregular text-grey-6 text-right text-no-wrap">
-                    {{ $t('dashboard_client.today_schedules.end_time_label') }} 
-                    <span class="gradient-diarista">{{ item.end_time?.slice(0, 5) }}</span>
+                  <q-icon
+                    class="q-mr-xs flex-shrink-0 gradient-diarista"
+                    name="mdi-clock-outline"
+                    size="13px"
+                  />
+
+                  <span
+                    class="font9 fontregular text-grey-6 text-right text-no-wrap"
+                  >
+                    {{ $t("dashboard_client.today_schedules.end_time_label") }}
+
+                    <span class="gradient-diarista">
+                      {{ item.end_time?.slice(0, 5) }}
+                    </span>
                   </span>
                 </template>
               </template>
@@ -107,44 +205,50 @@
 </template>
 
 <script setup>
-import { useQuasar } from 'quasar'
-import ProfileHelpDialog from 'src/components/profile/ProfileHelpDialog.vue'
-import {getFirstName} from 'src/helpers/utils'
+import { avatarColors } from "src/helpers/avatarColors";
+import { getFirstName } from "src/helpers/utils";
+import { useQuasar } from "quasar";
+
+import ProfileHelpDialog from "src/components/profile/ProfileHelpDialog.vue";
 
-defineProps({ data: { type: Array, default: () => [] } })
-const emit = defineEmits(['rate'])
+defineProps({ data: { type: Array, default: () => [] } });
 
-const $q = useQuasar()
+const emit = defineEmits(["rate"]);
 
-const avatarColors = [
-  { background: '#ffd5df', color: '#932e57' },
-  { background: '#d7e8ff', color: '#2158a8' },
-  { background: '#dfd',    color: '#2a7a3b' },
-  { background: '#ffe5cc', color: '#8a4500' },
-]
+const $q = useQuasar();
 
 const cardState = (item) => {
-  if (!item.code_verified) return 'awaiting_code'
-  const [h, m] = (item.end_time || '23:59').slice(0, 5).split(':').map(Number)
-  const endTime = new Date()
-  endTime.setHours(h, m, 0, 0)
-  return new Date() >= endTime ? 'finished' : 'in_progress'
-}
+  if (!item.code_verified) return "awaiting_code";
+
+  const [h, m] = (item.end_time || "23:59").slice(0, 5).split(":").map(Number);
+
+  const endTime = new Date();
+
+  endTime.setHours(h, m, 0, 0);
+
+  return new Date() >= endTime ? "finished" : "in_progress";
+};
 
 const progressByState = (item) => {
-  const state = cardState(item)
-  if (state === 'awaiting_code') return 60
-  if (state === 'in_progress')   return 80
-  return 100
-}
+  const state = cardState(item);
+
+  if (state === "awaiting_code") return 60;
+  if (state === "in_progress")   return 80;
+
+  return 100;
+};
+
+//
 
 const openHelp = () => {
-  $q.dialog({ component: ProfileHelpDialog })
-}
+  $q.dialog({ component: ProfileHelpDialog });
+};
 </script>
 
 <style scoped lang="scss">
-.scroll-wrapper { overflow: hidden; }
+.scroll-wrapper {
+  overflow: hidden;
+}
 .scroll-track {
   display: flex;
   flex-direction: row;
@@ -153,8 +257,13 @@ const openHelp = () => {
   overscroll-behavior-x: contain;
   scroll-snap-type: x proximity;
   padding-bottom: 8px;
-  &::-webkit-scrollbar { display: none; }
-  &::after { content: ''; flex: 0 0 1px; }
+  &::-webkit-scrollbar {
+    display: none;
+  }
+  &::after {
+    content: "";
+    flex: 0 0 1px;
+  }
 }
 
 .today-card {
@@ -174,7 +283,7 @@ const openHelp = () => {
   width: 26px;
   height: 26px;
   border-radius: 50%;
-  background: linear-gradient(135deg, #8B5CF6, #EC4899);
+  background: linear-gradient(135deg, #8b5cf6, #ec4899);
   display: flex;
   align-items: center;
   justify-content: center;
@@ -185,7 +294,7 @@ const openHelp = () => {
 }
 
 .rate-btn {
-  background: #EC4899;
+  background: #ec4899;
   color: white;
   white-space: nowrap;
   border-radius: 10px !important;
@@ -206,7 +315,7 @@ const openHelp = () => {
 .progress-track {
   width: 100%;
   height: 5px;
-  background: #E2E8F0;
+  background: #e2e8f0;
   border-radius: 3px;
   overflow: hidden;
 }
@@ -214,12 +323,11 @@ const openHelp = () => {
 .progress-fill {
   height: 100%;
   border-radius: 3px;
-  background: linear-gradient(90deg, #8B5CF6, #EC4899);
+  background: linear-gradient(90deg, #8b5cf6, #ec4899);
   transition: width 0.4s ease;
 
   &--finished {
     background: #22c55e;
   }
 }
-
 </style>

+ 133 - 84
src/components/dashboard/NextSchedulesDetailsDialog.vue

@@ -1,21 +1,42 @@
 <template>
-  <q-dialog ref="dialogRef" @hide="onDialogHide">
-    <q-card class="next-schedule-dialog-card bg-surface" :flat="false">
-
+  <q-dialog
+    ref="dialogRef"
+    @hide="onDialogHide"
+  >
+    <q-card
+      class="next-schedule-dialog-card bg-surface"
+      :flat="false"
+    >
       <q-card-section class="column items-center q-pt-lg q-pb-sm">
-        <q-avatar size="80px" :style="avatarStyle" class="fontbold text-h5 q-mb-sm">
-          <img v-if="details?.provider_photo" :src="details.provider_photo" />
+        <q-avatar
+          class="fontbold text-h5 q-mb-sm"
+          size="80px"
+          :style="avatarStyle"
+        >
+          <img
+            v-if="details?.provider_photo"
+            :src="details.provider_photo"
+          />
 
           <span v-else>
-            {{ schedule.provider_name?.slice(0, 2).toUpperCase() ?? '??' }}
+            {{ schedule.provider_name?.slice(0, 2).toUpperCase() ?? "??" }}
           </span>
         </q-avatar>
 
         <div class="provider-name font16 fontbold">
-          {{ getFirstName(schedule.provider_name) || '—' }}
+          {{ getFirstName(schedule.provider_name) || "—" }}
 
-          <span v-if="providerAge !== null" class="text-grey-6">
-            {{ '(' + providerAge + ' ' + $t('dashboard_client.next_schedules.provider_age_unit') + ')' }}
+          <span
+            v-if="providerAge !== null"
+            class="text-grey-6"
+          >
+            {{
+              "(" +
+              providerAge +
+              " " +
+              $t("dashboard_client.next_schedules.provider_age_unit") +
+              ")"
+            }}
           </span>
         </div>
 
@@ -23,8 +44,16 @@
           {{ schedule.address?.district }}
         </div>
 
-        <div v-if="schedule.address" class="font9 fontmedium text-grey-7 q-mt-xs">
-          <q-icon name="mdi-map-marker" color="text" size="14px" class="q-mr-xs" />
+        <div
+          v-if="schedule.address"
+          class="font9 fontmedium text-grey-7 q-mt-xs"
+        >
+          <q-icon
+            class="q-mr-xs"
+            color="text"
+            name="mdi-map-marker"
+            size="14px"
+          />
 
           {{ formatAddress(schedule.address) }}
         </div>
@@ -35,7 +64,10 @@
       <q-card-section class="q-py-sm">
         <template v-if="loadingDetails">
           <div class="row justify-center q-py-sm">
-            <q-spinner-dots color="primary" size="24px" />
+            <q-spinner-dots
+              color="primary"
+              size="24px"
+            />
           </div>
         </template>
 
@@ -58,11 +90,18 @@
             </div>
           </div>
 
-          <div v-if="!details?.specialities?.length" class="row items-center q-gutter-x-sm">
-            <q-icon color="secondary" name="mdi-check" size="16px" />
+          <div
+            v-if="!details?.specialities?.length"
+            class="row items-center q-gutter-x-sm"
+          >
+            <q-icon
+              color="secondary"
+              name="mdi-check"
+              size="16px"
+            />
 
             <span class="text-grey-8 font10 fontmedium">
-              {{ $t('dashboard_client.next_schedules.default_service') }}
+              {{ $t("dashboard_client.next_schedules.default_service") }}
             </span>
           </div>
         </template>
@@ -73,7 +112,7 @@
       <q-card-section class="q-py-md q-px-lg font12 fontmedium">
         <div class="detail-row">
           <span class="detail-label text-primary q-pr-sm">
-            {{ $t('dashboard_client.pending_schedules.detail_date') }}
+            {{ $t("dashboard_client.pending_schedules.detail_date") }}
           </span>
 
           <span class="detail-value">
@@ -83,24 +122,26 @@
 
         <div class="detail-row">
           <span class="detail-label text-primary q-pr-sm">
-            {{ $t('dashboard_client.pending_schedules.detail_time') }}
+            {{ $t("dashboard_client.pending_schedules.detail_time") }}
           </span>
 
           <span class="detail-value">
-            {{ schedule.start_time?.slice(0, 5) }} {{ $t('dashboard_client.next_schedules.to') }} {{ schedule.end_time?.slice(0, 5) }}
+            {{ schedule.start_time?.slice(0, 5) }}
+            {{ $t("dashboard_client.next_schedules.to") }}
+            {{ schedule.end_time?.slice(0, 5) }}
           </span>
         </div>
 
         <div class="detail-row-total font16 fontbold">
           <span class="detail-label text-primary q-pr-sm">
-            {{ $t('dashboard_client.pending_schedules.detail_total') }}
+            {{ $t("dashboard_client.pending_schedules.detail_total") }}
           </span>
 
           <span class="total-value">
             {{ formatCurrency(total) }}
           </span>
         </div>
-        
+
         <q-separator class="q-my-sm divisoria-tracejada" />
       </q-card-section>
 
@@ -142,111 +183,119 @@
 </template>
 
 <script setup>
-import { computed, onMounted, ref } from 'vue'
-import { formatAddress, getFirstName,formatCurrency } from 'src/helpers/utils';
-import { getScheduleClienteDetails } from 'src/api/dashboard'
-import { getSchedulePaymentType, getScheduleTotalWithPlatformFee } from 'src/helpers/paymentPlatformFees'
-import { useDialogPluginComponent, useQuasar } from 'quasar'
-import { useI18n } from 'vue-i18n'
-import { usePaymentPlatformFees } from 'src/composables/usePaymentPlatformFees'
+import { avatarColors } from "src/helpers/avatarColors";
+import { computed, onMounted, ref } from "vue";
+import { formatAddress, formatCurrency, getFirstName } from "src/helpers/utils";
+import { getScheduleClienteDetails } from "src/api/dashboard";
+
+import {
+  getSchedulePaymentType,
+  getScheduleTotalWithPlatformFee,
+} from "src/helpers/paymentPlatformFees";
 
-import ProfileHelpDialog from 'src/components/profile/ProfileHelpDialog.vue'
-import ScheduleCancelDialog from './ScheduleCancelDialog.vue'
+import { useDialogPluginComponent, useQuasar } from "quasar";
+import { useI18n } from "vue-i18n";
+import { usePaymentPlatformFees } from "src/composables/usePaymentPlatformFees";
+
+import ProfileHelpDialog from "src/components/profile/ProfileHelpDialog.vue";
+import ScheduleCancelDialog from "./ScheduleCancelDialog.vue";
 
 const props = defineProps({
-  schedule: { type: Object, required: true }
-})
+  schedule: { type: Object, required: true },
+});
+
+defineEmits([...useDialogPluginComponent.emits]);
 
-defineEmits([...useDialogPluginComponent.emits])
+const { t } = useI18n();
+const $q = useQuasar();
 
-const { t } = useI18n()
-const $q    = useQuasar()
+const { dialogRef, onDialogHide, onDialogCancel } = useDialogPluginComponent();
 
-const { dialogRef, onDialogHide, onDialogCancel } = useDialogPluginComponent()
+const { platformFees, loadPlatformFees } = usePaymentPlatformFees();
 
-const { platformFees, loadPlatformFees } = usePaymentPlatformFees()
+const details = ref(null);
+const loadingDetails = ref(true);
 
-const details        = ref(null)
-const loadingDetails = ref(true)
+const avatarStyle = computed(
+  () => avatarColors[props.schedule.id % avatarColors.length],
+);
 
 const parseLocalDate = (dateStr) => {
-  if (!dateStr) return null
+  if (!dateStr) return null;
 
-  const s = String(dateStr)
+  const s = String(dateStr);
 
-  const iso = s.match(/^(\d{4})-(\d{2})-(\d{2})/)
+  const iso = s.match(/^(\d{4})-(\d{2})-(\d{2})/);
 
-  if (iso) return new Date(+iso[1], +iso[2] - 1, +iso[3])
+  if (iso) return new Date(+iso[1], +iso[2] - 1, +iso[3]);
 
-  const dmy = s.match(/^(\d{2})\/(\d{2})\/(\d{4})/)
+  const dmy = s.match(/^(\d{2})\/(\d{2})\/(\d{4})/);
 
-  if (dmy) return new Date(+dmy[3], +dmy[2] - 1, +dmy[1])
+  if (dmy) return new Date(+dmy[3], +dmy[2] - 1, +dmy[1]);
 
-  return null
-}
+  return null;
+};
 
 const fullDateLabel = computed(() => {
-  if (props.schedule.formatted_date) return props.schedule.formatted_date
+  if (props.schedule.formatted_date) return props.schedule.formatted_date;
 
-  const d = parseLocalDate(props.schedule.date)
+  const d = parseLocalDate(props.schedule.date);
 
-  if (!d) return props.schedule.date ?? ''
+  if (!d) return props.schedule.date ?? "";
 
-  return d.toLocaleDateString('pt-BR', { day: '2-digit', month: 'long', year: 'numeric' })
-})
+  return d.toLocaleDateString("pt-BR", {
+    day: "2-digit",
+    month: "long",
+    year: "numeric",
+  });
+});
 
 const providerAge = computed(() => {
-  if (!details.value?.provider_birth_date) return null
-
-  const birth = new Date(details.value.provider_birth_date)
+  if (!details.value?.provider_birth_date) return null;
 
-  const today = new Date()
+  const birth = new Date(details.value.provider_birth_date);
 
-  let age = today.getFullYear() - birth.getFullYear()
+  const today = new Date();
 
-  const m = today.getMonth() - birth.getMonth()
+  let age = today.getFullYear() - birth.getFullYear();
 
-  if (m < 0 || (m === 0 && today.getDate() < birth.getDate())) age--
+  const m = today.getMonth() - birth.getMonth();
 
-  return age
-})
+  if (m < 0 || (m === 0 && today.getDate() < birth.getDate())) age--;
 
-const total = computed(() => getScheduleTotalWithPlatformFee(
-  props.schedule,
-  getSchedulePaymentType(props.schedule),
-  platformFees.value
-))
+  return age;
+});
 
-const avatarColors = [
-  { background: '#ffd5df', color: '#932e57' },
-  { background: '#d7e8ff', color: '#2158a8' },
-  { background: '#dfd',    color: '#2a7a3b' },
-  { background: '#ffe5cc', color: '#8a4500' },
-]
-const avatarStyle = computed(() => avatarColors[props.schedule.id % avatarColors.length])
+const total = computed(() =>
+  getScheduleTotalWithPlatformFee(
+    props.schedule,
+    getSchedulePaymentType(props.schedule),
+    platformFees.value,
+  ),
+);
 
 const openCancelDialog = () => {
   $q.dialog({
-    component: ScheduleCancelDialog, componentProps: { schedule: props.schedule }
-  })
-}
+    component: ScheduleCancelDialog,
+    componentProps: { schedule: props.schedule },
+  });
+};
 
 const openHelp = () => {
-  $q.dialog({ component: ProfileHelpDialog })
-}
+  $q.dialog({ component: ProfileHelpDialog });
+};
 
 onMounted(async () => {
   try {
-    details.value = await getScheduleClienteDetails(props.schedule.id)
+    details.value = await getScheduleClienteDetails(props.schedule.id);
 
-    loadPlatformFees().catch(() => {})
+    loadPlatformFees().catch(() => {});
   } catch {
-    $q.notify({ message: t('http.errors.failed'), color: 'negative' })
+    $q.notify({ message: t("http.errors.failed"), color: "negative" });
   } finally {
-    loadingDetails.value = false
+    loadingDetails.value = false;
   }
-})
-
+});
 </script>
 
 <style scoped lang="scss">
@@ -258,7 +307,7 @@ onMounted(async () => {
 }
 
 .provider-name {
-  color: #8B5CF6;
+  color: #8b5cf6;
 }
 
 .detail-row {

+ 117 - 69
src/components/dashboard/ScheduleAcceptedDialog.vue

@@ -1,24 +1,33 @@
 <template>
-  <q-dialog ref="dialogRef" @hide="onDialogHide">
-    <q-card class="accepted-dialog-card bg-surface" :flat="false">
-
+  <q-dialog
+    ref="dialogRef"
+    @hide="onDialogHide"
+  >
+    <q-card
+      class="accepted-dialog-card bg-surface"
+      :flat="false"
+    >
       <q-card-section class="column items-center q-pt-lg q-pb-sm">
-        <q-avatar size="80px" :style="avatarStyle" class="text-h5 q-mb-sm">
-          {{ schedule.provider_name?.slice(0, 2).toUpperCase() ?? '??' }}
+        <q-avatar
+          class="text-h5 q-mb-sm"
+          size="80px"
+          :style="avatarStyle"
+        >
+          {{ schedule.provider_name?.slice(0, 2).toUpperCase() ?? "??" }}
         </q-avatar>
 
         <div class="font16 fontbold provider-name">
-          {{ getFirstName(schedule.provider_name) || '—' }}
+          {{ getFirstName(schedule.provider_name) || "—" }}
         </div>
 
         <div class="font14 fontmedium text-text">
-          {{ schedule.address?.district || '' }}
+          {{ schedule.address?.district || "" }}
         </div>
       </q-card-section>
 
       <q-card-section class="text-center q-pt-xs q-pb-md">
         <div class="accepted-title font16 fontbold">
-          {{ $t('dashboard_client.pending_schedules.accepted_title') }}
+          {{ $t("dashboard_client.pending_schedules.accepted_title") }}
         </div>
       </q-card-section>
 
@@ -27,26 +36,32 @@
       <q-card-section class="q-py-md q-px-lg">
         <div class="detail-row">
           <span class="text-primary font14 fontmedium">
-            {{ $t('dashboard_client.pending_schedules.detail_date') }}
+            {{ $t("dashboard_client.pending_schedules.detail_date") }}
           </span>
 
           <span class="detail-value">
             {{ formattedDate }}
           </span>
         </div>
+
         <div class="detail-row">
           <span class="text-primary font14 fontmedium">
-            {{ $t('dashboard_client.pending_schedules.detail_time') }}
+            {{ $t("dashboard_client.pending_schedules.detail_time") }}
           </span>
 
           <span class="detail-valued text-text">
-            {{ schedule.start_time?.slice(0, 5) }} {{ $t('dashboard_client.next_schedules.to') }} {{ schedule.end_time?.slice(0, 5) }}
+            {{ schedule.start_time?.slice(0, 5) }}
+            {{ $t("dashboard_client.next_schedules.to") }}
+            {{ schedule.end_time?.slice(0, 5) }}
           </span>
         </div>
 
-        <div v-if="hasCartDiscount" class="detail-row">
+        <div
+          v-if="hasCartDiscount"
+          class="detail-row"
+        >
           <span class="text-primary font14 fontmedium">
-            {{ $t('dashboard_client.pending_schedules.detail_total') }}
+            {{ $t("dashboard_client.pending_schedules.detail_total") }}
           </span>
 
           <span class="total-value font14 fontbold">
@@ -54,9 +69,12 @@
           </span>
         </div>
 
-        <div v-if="!hasCartDiscount" class="detail-row">
+        <div
+          v-if="!hasCartDiscount"
+          class="detail-row"
+        >
           <span class="text-primary font14 fontmedium">
-            {{ $t('dashboard_client.pending_schedules.detail_pix_total') }}
+            {{ $t("dashboard_client.pending_schedules.detail_pix_total") }}
           </span>
 
           <span class="total-value font14 fontbold">
@@ -64,9 +82,12 @@
           </span>
         </div>
 
-        <div v-if="!hasCartDiscount && pixDiscount > 0" class="detail-row discount-row">
+        <div
+          v-if="!hasCartDiscount && pixDiscount > 0"
+          class="detail-row discount-row"
+        >
           <span class="font13 fontmedium">
-            {{ $t('dashboard_client.pending_schedules.detail_pix_discount') }}
+            {{ $t("dashboard_client.pending_schedules.detail_pix_discount") }}
           </span>
 
           <span class="font13 fontbold">
@@ -74,9 +95,14 @@
           </span>
         </div>
 
-        <div v-if="!hasCartDiscount" class="detail-row">
+        <div
+          v-if="!hasCartDiscount"
+          class="detail-row"
+        >
           <span class="text-primary font14 fontmedium">
-            {{ $t('dashboard_client.pending_schedules.detail_credit_card_total') }}
+            {{
+              $t("dashboard_client.pending_schedules.detail_credit_card_total")
+            }}
           </span>
 
           <span class="total-value font14 fontbold">
@@ -105,89 +131,111 @@
           @click="onDialogHide"
         />
       </q-card-section>
-
     </q-card>
   </q-dialog>
 </template>
 
 <script setup>
-import { computed, onMounted } from 'vue'
-import { formatCurrency, getFirstName } from 'src/helpers/utils'
-import { getSchedulePlatformFeeRate, scheduleUsesCartDiscount } from 'src/helpers/paymentPlatformFees'
-import { useDialogPluginComponent, useQuasar } from 'quasar'
-import { usePaymentStore } from 'src/stores/payment'
-import { usePaymentPlatformFees } from 'src/composables/usePaymentPlatformFees'
-import SchedulePaymentDialog from './SchedulePaymentDialog.vue'
-import SchedulePaymentPixDialog from './SchedulePaymentPixDialog.vue'
+import { avatarColors } from "src/helpers/avatarColors";
+import { computed, onMounted } from "vue";
+import { formatCurrency, getFirstName } from "src/helpers/utils";
+
+import {
+  getSchedulePlatformFeeRate,
+  scheduleUsesCartDiscount,
+} from "src/helpers/paymentPlatformFees";
+
+import { useDialogPluginComponent, useQuasar } from "quasar";
+import { usePaymentPlatformFees } from "src/composables/usePaymentPlatformFees";
+import { usePaymentStore } from "src/stores/payment";
+
+import SchedulePaymentDialog from "./SchedulePaymentDialog.vue";
+import SchedulePaymentPixDialog from "./SchedulePaymentPixDialog.vue";
 
 const props = defineProps({
-  schedule: { type: Object, required: true }
-})
+  schedule: { type: Object, required: true },
+});
 
-defineEmits([...useDialogPluginComponent.emits])
+defineEmits([...useDialogPluginComponent.emits]);
 
-const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent()
+const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent();
 
-const $q = useQuasar()
+const $q = useQuasar();
 
-const paymentStore = usePaymentStore()
+const paymentStore = usePaymentStore();
 
-const { platformFees, loadPlatformFees } = usePaymentPlatformFees()
+const { platformFees, loadPlatformFees } = usePaymentPlatformFees();
 
-const baseAmount = computed(() => Number(props.schedule.total_amount) || 0)
+const avatarStyle = computed(
+  () => avatarColors[props.schedule.id % avatarColors.length],
+);
 
-const creditCardTotal = computed(() => parseFloat((baseAmount.value + platformFee('credit_card')).toFixed(2)))
+const baseAmount = computed(() => Number(props.schedule.total_amount) || 0);
 
-const hasCartDiscount = computed(() => scheduleUsesCartDiscount(props.schedule))
+const creditCardTotal = computed(() =>
+  parseFloat((baseAmount.value + platformFee("credit_card")).toFixed(2)),
+);
+
+const hasCartDiscount = computed(() =>
+  scheduleUsesCartDiscount(props.schedule),
+);
 
 const formattedDate = computed(() => {
-  if (props.schedule.formatted_date) return props.schedule.formatted_date
+  if (props.schedule.formatted_date) return props.schedule.formatted_date;
 
-  const raw = String(props.schedule.date || '')
+  const raw = String(props.schedule.date || "");
 
-  const m = raw.match(/^(\d{4})-(\d{2})-(\d{2})/)
+  const m = raw.match(/^(\d{4})-(\d{2})-(\d{2})/);
 
-  if (!m) return raw
+  if (!m) return raw;
 
-  const d = new Date(+m[1], +m[2] - 1, +m[3])
+  const d = new Date(+m[1], +m[2] - 1, +m[3]);
 
-  return d.toLocaleDateString('pt-BR', { day: '2-digit', month: 'long', year: 'numeric' })
-})
+  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))))
-const pixTotal    = computed(() => parseFloat((baseAmount.value + platformFee('pix')).toFixed(2)))
+const pixDiscount = computed(() =>
+  Math.max(0, parseFloat((creditCardTotal.value - pixTotal.value).toFixed(2))),
+);
+const pixTotal = computed(() =>
+  parseFloat((baseAmount.value + platformFee("pix")).toFixed(2)),
+);
 
 const platformFee = (paymentType) => {
-  const feeRate = getSchedulePlatformFeeRate(props.schedule, paymentType, platformFees.value)
+  const feeRate = getSchedulePlatformFeeRate(
+    props.schedule,
+    paymentType,
+    platformFees.value,
+  );
 
-  return parseFloat((baseAmount.value * (feeRate ?? 0)).toFixed(2))
-}
+  return parseFloat((baseAmount.value * (feeRate ?? 0)).toFixed(2));
+};
 
 const onGoToPayment = () => {
-  const hasValidPixPayment = !!paymentStore.getValidPixPayment(props.schedule.id)
+  const hasValidPixPayment = !!paymentStore.getValidPixPayment(
+    props.schedule.id,
+  );
 
   $q.dialog({
-    component: hasValidPixPayment ? SchedulePaymentPixDialog : SchedulePaymentDialog,
+    component: hasValidPixPayment
+      ? SchedulePaymentPixDialog
+      : SchedulePaymentDialog,
     componentProps: {
-      schedule: props.schedule, ...(hasValidPixPayment ? { total: pixTotal.value } : {}),
+      schedule: props.schedule,
+      ...(hasValidPixPayment ? { total: pixTotal.value } : {}),
     },
   }).onOk(() => {
-    onDialogOK()
-  })
-}
-
-const avatarColors = [
-  { background: '#ffd5df', color: '#932e57' },
-  { background: '#d7e8ff', color: '#2158a8' },
-  { background: '#dfd',    color: '#2a7a3b' },
-  { background: '#ffe5cc', color: '#8a4500' },
-]
-
-const avatarStyle = computed(() => avatarColors[props.schedule.id % avatarColors.length])
+    onDialogOK();
+  });
+};
 
 onMounted(() => {
-  loadPlatformFees().catch(() => {})
-})
+  loadPlatformFees().catch(() => {});
+});
 </script>
 
 <style scoped lang="scss">
@@ -200,11 +248,11 @@ onMounted(() => {
 
 .accepted-title {
   line-height: 1.3;
-  color: #8B5CF6;
+  color: #8b5cf6;
 }
 
 .provider-name {
-  color: #8B5CF6;
+  color: #8b5cf6;
 }
 
 .detail-row {

+ 223 - 133
src/components/dashboard/ScheduleRatingDialog.vue

@@ -1,52 +1,93 @@
 <template>
-  <q-dialog ref="dialogRef" @hide="onDialogHide">
-    <q-card class="rating-dialog-card bg-surface shadow-card" :flat="false">
-
+  <q-dialog
+    ref="dialogRef"
+    @hide="onDialogHide"
+  >
+    <q-card
+      class="rating-dialog-card bg-surface shadow-card"
+      :flat="false"
+    >
       <div class="row justify-end q-pt-sm q-pr-sm">
-        <q-btn flat round dense icon="close" color="grey-6" size="sm" @click="onDialogCancel" />
+        <q-btn
+          color="grey-6"
+          dense
+          flat
+          icon="close"
+          round
+          size="sm"
+          @click="onDialogCancel"
+        />
       </div>
 
       <div class="column items-center q-pb-sm">
-        <q-avatar size="64px" class="q-mb-sm">
+        <q-avatar
+          class="q-mb-sm"
+          size="64px"
+        >
           <span
-            :style="avatarStyle"
             class="full-width full-height flex flex-center"
-            style="border-radius: 50%;"
+            style="border-radius: 50%"
+            :style="avatarStyle"
           >
             {{ initials }}
           </span>
         </q-avatar>
-        <div class="font16 fontbold text-text text-center q-px-lg" style="line-height:1.3">
-          {{ $t('dashboard_client.schedule_rating.title') }}
-          <span class="text-primary"> {{ (getFirstName(schedule.provider_name) || '—') + '?' }}</span>
+
+        <div
+          class="font16 fontbold text-text text-center q-px-lg"
+          style="line-height: 1.3"
+        >
+          {{ $t("dashboard_client.schedule_rating.title") }}
+
+          <span class="text-primary">
+            {{ (getFirstName(schedule.provider_name) || "—") + "?" }}
+          </span>
         </div>
       </div>
 
       <div class="column items-center q-pb-xs">
         <q-rating
           v-model="stars"
-          :max="5"
-          size="lg"
           color="amber"
           icon="mdi-star-outline"
           icon-selected="mdi-star"
+          size="lg"
+          :max="5"
           @update:model-value="onStarsChange"
         />
       </div>
 
-      <q-card-section v-if="stars > 0" class="q-pt-xs q-pb-xs">
+      <q-card-section
+        v-if="stars > 0"
+        class="q-pt-xs q-pb-xs"
+      >
         <div class="text-grey-7 font14 fontbold text-center q-mb-sm">
-          {{ isNegative ? $t('dashboard_client.schedule_rating.negative_label') : $t('dashboard_client.schedule_rating.positive_label') }}
+          {{
+            isNegative
+              ? $t("dashboard_client.schedule_rating.negative_label")
+              : $t("dashboard_client.schedule_rating.positive_label")
+          }}
         </div>
-        <div v-if="loadingTags" class="row justify-center q-py-sm">
-          <q-spinner-dots color="primary" size="24px" />
+
+        <div
+          v-if="loadingTags"
+          class="row justify-center q-py-sm"
+        >
+          <q-spinner-dots
+            color="primary"
+            size="24px"
+          />
         </div>
-        <div v-else class="row justify-center q-gutter-xs">
+
+        <div
+          v-else
+          class="row justify-center q-gutter-xs"
+        >
           <div
             v-for="tag in tags"
             :key="tag.id"
-            class="tag-pill"
             :class="{ 'tag-pill--selected': selectedTagIds.includes(tag.id) }"
+            class="tag-pill"
             @click="toggleTag(tag.id)"
           >
             {{ tag.description }}
@@ -56,171 +97,207 @@
 
       <q-card-section class="q-pt-xs q-pb-xs q-px-lg">
         <div class="font14 fontbold text-text q-mb-xs text-center">
-          {{ $t('dashboard_client.schedule_rating.comment_placeholder') }}
+          {{ $t("dashboard_client.schedule_rating.comment_placeholder") }}
         </div>
+
         <q-input
           v-model="comment"
-          type="textarea"
-          outlined
-          dense
-          rows="3"
           color="primary"
-          input-class="text-black"
+          dense
           hide-bottom-space
+          input-class="text-black"
+          outlined
+          rows="3"
+          type="textarea"
         />
       </q-card-section>
 
       <q-card-section class="q-pt-xs q-pb-xs q-px-lg">
         <input
           ref="photoInputRef"
-          type="file"
           accept="image/jpeg,image/png,image/webp"
-          multiple
           class="hidden"
+          multiple
+          type="file"
           @change="onPhotosSelected"
         />
+
         <div class="row q-gutter-xs">
-          <div v-for="(preview, idx) in photoPreviews" :key="idx" class="photo-thumb">
+          <div
+            v-for="(preview, idx) in photoPreviews"
+            :key="idx"
+            class="photo-thumb"
+          >
             <img :src="preview" />
-            <q-btn round dense flat icon="close" size="xs" class="photo-thumb__remove" @click="removePhoto(idx)" />
+
+            <q-btn
+              class="photo-thumb__remove"
+              dense
+              flat
+              icon="close"
+              round
+              size="xs"
+              @click="removePhoto(idx)"
+            />
           </div>
-          <div v-if="photos.length < 5" class="photo-add" @click="photoInputRef.click()">
-            <q-icon name="mdi-camera-plus-outline" size="24px" color="grey-5" />
-            <div class="photo-add__label">{{ $t('dashboard_client.schedule_rating.add_photo') }}</div>
+
+          <div
+            v-if="photos.length < 5"
+            class="photo-add"
+            @click="photoInputRef.click()"
+          >
+            <q-icon
+              color="grey-5"
+              name="mdi-camera-plus-outline"
+              size="24px"
+            />
+
+            <div class="photo-add__label">
+              {{ $t("dashboard_client.schedule_rating.add_photo") }}
+            </div>
           </div>
         </div>
       </q-card-section>
 
-      <q-card-section v-if="stars > 0" class="q-pt-xs q-pb-xs q-px-lg">
+      <q-card-section
+        v-if="stars > 0"
+        class="q-pt-xs q-pb-xs q-px-lg"
+      >
         <q-checkbox
           v-model="checkboxValue"
-          :label="isNegative ? $t('dashboard_client.schedule_rating.block_label') : $t('dashboard_client.schedule_rating.favorite_label')"
+          checked-icon="mdi-check-circle"
+          class="text-text"
           color="primary"
           keep-color
-          class="text-text"
-          checked-icon="mdi-check-circle"
           unchecked-icon="mdi-checkbox-blank-circle-outline"
+          :label="isNegative ? $t('dashboard_client.schedule_rating.block_label') : $t('dashboard_client.schedule_rating.favorite_label')"
         />
       </q-card-section>
 
       <q-card-section class="q-pt-sm q-pb-xs q-px-lg row">
         <q-btn
-          unelevated
-          rounded
-          no-caps
-          full-width
+          class="submit-btn col-12"
           color="primary"
+          full-width
+          no-caps
           padding="8px 12px"
-          class="submit-btn col-12"
+          rounded
+          unelevated
+          :disable="stars === 0"
           :label="$t('dashboard_client.schedule_rating.submit_btn')"
           :loading="loading"
-          :disable="stars === 0"
           @click="submit"
         />
       </q-card-section>
 
       <q-card-section class="q-pt-xs q-pb-lg text-center">
-        <span class="text-text font12 fontbold cursor-pointer" @click="openHelp">
-          {{ $t('dashboard_client.schedule_rating.help_link') }}
+        <span
+          class="text-text font12 fontbold cursor-pointer"
+          @click="openHelp"
+        >
+          {{ $t("dashboard_client.schedule_rating.help_link") }}
         </span>
       </q-card-section>
-
     </q-card>
   </q-dialog>
 </template>
 
 <script setup>
-import { ref, computed, onMounted } from 'vue'
-import { useDialogPluginComponent, useQuasar } from 'quasar'
-import { useI18n } from 'vue-i18n'
-import { createReview, getImprovementTypes } from 'src/api/review'
-import { userStore } from 'src/stores/user'
-import ProfileHelpDialog from 'src/components/profile/ProfileHelpDialog.vue'
-import { getFirstName } from 'src/helpers/utils'
+import { avatarColors } from "src/helpers/avatarColors";
+import { computed, onMounted, ref } from "vue";
+import { createReview, getImprovementTypes } from "src/api/review";
+import { getFirstName } from "src/helpers/utils";
+import { useDialogPluginComponent, useQuasar } from "quasar";
+import { useI18n } from "vue-i18n";
+import { userStore } from "src/stores/user";
+
+import ProfileHelpDialog from "src/components/profile/ProfileHelpDialog.vue";
 
 const props = defineProps({
   schedule: {
     type: Object,
-    required: true
-  }
-})
-
-defineEmits([...useDialogPluginComponent.emits])
-const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginComponent()
-const { t } = useI18n()
-const $q = useQuasar()
-const store = userStore()
-
-const stars = ref(0)
-const selectedTagIds = ref([])
-const comment = ref(null)
-const checkboxValue = ref(false)
-const tags = ref([])
-const loadingTags = ref(false)
-const loading = ref(false)
-const photos = ref([])
-const photoPreviews = ref([])
-const photoInputRef = ref(null)
-
-const isNegative = computed(() => stars.value > 0 && stars.value <= 2)
-const isPositive = computed(() => stars.value >= 3)
-
-const avatarColors = [
-  { background: '#ffd5df', color: '#932e57' },
-  { background: '#d7e8ff', color: '#2158a8' },
-  { background: '#dfd',    color: '#2a7a3b' },
-  { background: '#ffe5cc', color: '#8a4500' },
-]
+    required: true,
+  },
+});
+
+defineEmits([...useDialogPluginComponent.emits]);
+
+const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } =
+  useDialogPluginComponent();
+
+const { t } = useI18n();
+const $q = useQuasar();
+const store = userStore();
+
+const stars = ref(0);
+const selectedTagIds = ref([]);
+const comment = ref(null);
+const checkboxValue = ref(false);
+const tags = ref([]);
+const loadingTags = ref(false);
+const loading = ref(false);
+const photos = ref([]);
+const photoPreviews = ref([]);
+const photoInputRef = ref(null);
 
 const avatarStyle = computed(() => {
-  const c = avatarColors[props.schedule.provider_id % avatarColors.length]
-  return { background: c.background, color: c.color }
-})
+  const c = avatarColors[props.schedule.provider_id % avatarColors.length];
+
+  return { background: c.background, color: c.color };
+});
 
 const initials = computed(() => {
-  const firstName = getFirstName(props.schedule.provider_name)
+  const firstName = getFirstName(props.schedule.provider_name);
 
-  return firstName?.slice(0, 2).toUpperCase() ?? '??'
-})
+  return firstName?.slice(0, 2).toUpperCase() ?? "??";
+});
 
-const onStarsChange = () => {
-  selectedTagIds.value = []
-  checkboxValue.value = false
-}
-
-const toggleTag = (id) => {
-  const idx = selectedTagIds.value.indexOf(id)
-  if (idx === -1) selectedTagIds.value.push(id)
-  else selectedTagIds.value.splice(idx, 1)
-}
+const isNegative = computed(() => stars.value > 0 && stars.value <= 2);
+const isPositive = computed(() => stars.value >= 3);
 
 const onPhotosSelected = (event) => {
-  const files = Array.from(event.target.files)
-  const remaining = 5 - photos.value.length
-  files.slice(0, remaining).forEach(file => {
-    photos.value.push(file)
-    photoPreviews.value.push(URL.createObjectURL(file))
-  })
-  event.target.value = ''
-}
+  const files = Array.from(event.target.files);
 
-const removePhoto = (idx) => {
-  URL.revokeObjectURL(photoPreviews.value[idx])
-  photos.value.splice(idx, 1)
-  photoPreviews.value.splice(idx, 1)
-}
+  const remaining = 5 - photos.value.length;
+
+  files.slice(0, remaining).forEach((file) => {
+    photos.value.push(file);
+    photoPreviews.value.push(URL.createObjectURL(file));
+  });
+
+  event.target.value = "";
+};
+
+const onStarsChange = () => {
+  selectedTagIds.value = [];
+  checkboxValue.value = false;
+};
 
 const openHelp = () => {
-  $q.dialog({ component: ProfileHelpDialog })
-}
+  $q.dialog({ component: ProfileHelpDialog });
+};
+
+const removePhoto = (idx) => {
+  URL.revokeObjectURL(photoPreviews.value[idx]);
+  photos.value.splice(idx, 1);
+  photoPreviews.value.splice(idx, 1);
+};
+
+const toggleTag = (id) => {
+  const idx = selectedTagIds.value.indexOf(id);
+  if (idx === -1) selectedTagIds.value.push(id);
+  else selectedTagIds.value.splice(idx, 1);
+};
 
 const submit = async () => {
-  if (stars.value === 0) return
-  loading.value = true
+  if (stars.value === 0) return;
+
+  loading.value = true;
+
   try {
     await createReview({
       schedule_id: props.schedule.id,
-      origin: 'client',
+      origin: "client",
       origin_id: store.user.client.id,
       stars: stars.value,
       comment: comment.value || null,
@@ -229,32 +306,39 @@ const submit = async () => {
       block_client: false,
       favorite_provider: isPositive.value && checkboxValue.value,
       photos: photos.value,
-    })
+    });
 
-    onDialogOK(true)
+    onDialogOK(true);
   } catch (error) {
-    const status = error?.response?.status
+    const status = error?.response?.status;
+
     if (status === 422) {
-      $q.notify({ message: t('dashboard_client.schedule_rating.already_reviewed'), color: 'negative', icon: 'mdi-alert-circle-outline' })
+      $q.notify({
+        message: t("dashboard_client.schedule_rating.already_reviewed"),
+        color: "negative",
+        icon: "mdi-alert-circle-outline",
+      });
     } else {
-      $q.notify({ message: t('http.errors.failed'), color: 'negative' })
+      $q.notify({ message: t("http.errors.failed"), color: "negative" });
     }
   } finally {
-    loading.value = false
+    loading.value = false;
   }
-}
+};
 
 onMounted(async () => {
-  loadingTags.value = true
+  loadingTags.value = true;
+
   try {
-    const result = await getImprovementTypes('client')
-    tags.value = result ?? []
+    const result = await getImprovementTypes("client");
+
+    tags.value = result ?? [];
   } catch {
-    tags.value = []
+    tags.value = [];
   } finally {
-    loadingTags.value = false
+    loadingTags.value = false;
   }
-})
+});
 </script>
 
 <style scoped lang="scss">
@@ -271,12 +355,14 @@ onMounted(async () => {
   padding: 5px 14px;
   color: #6b7280;
   cursor: pointer;
-  transition: border-color 0.15s, color 0.15s;
+  transition:
+    border-color 0.15s,
+    color 0.15s;
   user-select: none;
 
   &--selected {
-    border-color: #8B5CF6;
-    color: #8B5CF6;
+    border-color: #8b5cf6;
+    color: #8b5cf6;
   }
 }
 
@@ -327,8 +413,12 @@ onMounted(async () => {
     line-height: 1.2;
   }
 
-  &:active { background: rgba(0, 0, 0, 0.03); }
+  &:active {
+    background: rgba(0, 0, 0, 0.03);
+  }
 }
 
-.hidden { display: none; }
+.hidden {
+  display: none;
+}
 </style>

+ 77 - 43
src/components/defaults/DefaultInputDatePicker.vue

@@ -3,24 +3,38 @@
     <q-input
       ref="inputRef"
       v-model="treatedDate"
-      :mask="inputMask"
-      :label
-      :rules
+      clearable
       :dense
       :error
       :error-message
-      clearable
+      :label
+      :mask="inputMask"
+      :rules
     >
       <template #append>
         <q-icon
-          :name="time ? 'mdi-calendar-clock' : 'mdi-calendar'"
           class="cursor-pointer"
+          :name="time ? 'mdi-calendar-clock' : 'mdi-calendar'"
         >
-          <q-popup-proxy cover transition-show="scale" transition-hide="scale">
+          <q-popup-proxy
+            cover
+            transition-hide="scale"
+            transition-show="scale"
+          >
             <template v-if="!time">
-              <q-date v-model="date" mask="YYYY-MM-DD" color="primary" class="bg-surface text-text calendar-custom">
+              <q-date
+                v-model="date"
+                color="primary"
+                mask="YYYY-MM-DD"
+                :class="['bg-surface', 'text-text', 'calendar-custom', calendarClass]"
+              >
                 <div class="row items-center justify-end">
-                  <q-btn v-close-popup label="Close" color="primary" flat />
+                  <q-btn
+                    v-close-popup
+                    color="primary"
+                    flat
+                    label="Close"
+                  />
                 </div>
               </q-date>
             </template>
@@ -29,11 +43,14 @@
               <q-tab-panels
                 v-model="activePanel"
                 animated
-                transition-prev="slide-right"
-                transition-next="slide-left"
                 class="bg-white"
+                transition-next="slide-left"
+                transition-prev="slide-right"
               >
-                <q-tab-panel name="date" class="q-pa-none">
+                <q-tab-panel
+                  class="q-pa-none"
+                  name="date"
+                >
                   <q-date
                     v-model="date"
                     mask="YYYY-MM-DD HH:mm"
@@ -41,8 +58,15 @@
                   />
                 </q-tab-panel>
 
-                <q-tab-panel name="time" class="q-pa-none">
-                  <q-time v-model="date" mask="YYYY-MM-DD HH:mm" format24h />
+                <q-tab-panel
+                  class="q-pa-none"
+                  name="time"
+                >
+                  <q-time
+                    v-model="date"
+                    format24h
+                    mask="YYYY-MM-DD HH:mm"
+                  />
                 </q-tab-panel>
               </q-tab-panels>
             </template>
@@ -54,27 +78,22 @@
 </template>
 
 <script setup>
-import { watch, ref, computed, useTemplateRef } from "vue";
+import { computed, ref, useTemplateRef, watch } from "vue";
 import { useI18n } from "vue-i18n";
 import masks from "src/helpers/masks";
 
-const { label, rules, time, dense } = defineProps({
-  label: {
+const treatedDate = defineModel();
+const untreatedDate = defineModel("untreatedDate");
+
+const { label, rules, time, dense, calendarClass } = defineProps({
+  calendarClass: {
     type: String,
-    default: () => useI18n().t("common.terms.date"),
-  },
-  rules: {
-    type: Array,
-    default: () => [],
+    default: "",
   },
   dense: {
     type: Boolean,
     default: false,
   },
-  time: {
-    type: Boolean,
-    default: false,
-  },
   error: {
     type: Boolean,
     default: false,
@@ -83,53 +102,65 @@ const { label, rules, time, dense } = defineProps({
     type: String,
     default: "",
   },
+  label: {
+    type: String,
+    default: () => useI18n().t("common.terms.date"),
+  },
+  rules: {
+    type: Array,
+    default: () => [],
+  },
+  time: {
+    type: Boolean,
+    default: false,
+  },
 });
 
 const qInputRef = useTemplateRef("inputRef");
 
-const treatedDate = defineModel();
-const untreatedDate = defineModel("untreatedDate");
-
 const activePanel = ref("date");
 const date = ref();
 
-const handleDateSelection = () => {
+const inputMask = computed(() => {
+  if (!qInputRef.value) return "";
+
   if (time) {
-    activePanel.value = "time";
+    return masks.Brasil.datetime;
   }
-};
+  return masks.Brasil.date;
+});
 
 const formatDate = (value) => {
   if (!value) return null;
 
   const [datePart, timePart] = value.split(" ");
+
   const formattedDate = datePart.split("-").reverse().join("/");
 
   return time && timePart ? `${formattedDate} ${timePart}` : formattedDate;
 };
 
+const handleDateSelection = () => {
+  if (time) {
+    activePanel.value = "time";
+  }
+};
+
 const unformatDate = (value) => {
   if (!value) return null;
 
   const [datePart, timePart] = value.split(" ");
+
   const formattedDate = datePart.split("/").reverse().join("-");
 
   return time && timePart ? `${formattedDate} ${timePart}` : formattedDate;
 };
 
-const inputMask = computed(() => {
-  if (!qInputRef.value) return "";
-
-  if (time) {
-    return masks.Brasil.datetime;
-  }
-  return masks.Brasil.date;
-});
-
 watch(date, (value) => {
   if (!value) return;
 
   untreatedDate.value = value;
+
   treatedDate.value = formatDate(value);
 });
 
@@ -139,8 +170,10 @@ watch(treatedDate, (value) => {
     untreatedDate.value = null;
     treatedDate.value = null;
     activePanel.value = "date";
+
     return;
   }
+
   date.value = unformatDate(value);
 });
 
@@ -152,15 +185,16 @@ watch(
       untreatedDate.value = null;
       treatedDate.value = null;
       activePanel.value = "date";
+
       return;
     }
+
     date.value = value;
+
     treatedDate.value = formatDate(value);
   },
   { immediate: true },
 );
 </script>
 
-<style scoped lang="scss">
-
-</style>
+<style scoped lang="scss"></style>

+ 0 - 29
src/components/layout/ScheduleCartMenu.vue

@@ -1,29 +0,0 @@
-<!-- eslint-disable @intlify/vue-i18n/no-raw-text -->
-<template>
-  <q-btn
-    class="schedule-cart-button"
-    color="primary"
-    icon="mdi-cart-outline"
-    round
-    unelevated
-    :to="{ name: 'ScheduleCartPage' }"
-  >
-    <q-badge v-if="cart.items.length" color="secondary" floating rounded>
-      {{ cart.items.length }}
-    </q-badge>
-
-    <q-tooltip>{{ $t('schedule_cart.title') }}</q-tooltip>
-  </q-btn>
-</template>
-
-<script setup>
-import { useScheduleCartStore } from "src/stores/scheduleCart";
-
-const cart = useScheduleCartStore();
-</script>
-
-<style scoped lang="scss">
-.schedule-cart-button {
-  box-shadow: 0 8px 24px rgba(166, 61, 247, 0.28);
-}
-</style>

+ 44 - 31
src/components/login/LoginStep1Panel.vue

@@ -1,57 +1,63 @@
 <template>
   <q-card-section class="full-width">
     <div class="text-text">
-      <span class="font12 fontbold">{{ $t("common.terms.email") }}</span>
+      <span class="font12 fontbold">
+        {{ $t("common.terms.email") }}
+      </span>
     </div>
 
     <q-input
       v-model="email"
       class="bg-surface q-mt-sm"
       hide-bottom-space
-      input-class="text-text font12 fontregular"
+      input-class="font12 fontmedium text-text"
       lazy-rules
       no-error-icon
       outlined
       rounded
       :bottom-slots="false"
       :placeholder="$t('common.terms.email')"
-      :rules="!phone ? [inputRules.requiredHideMessage, inputRules.email] : []"
+      :rules="!smsEnabled || !phone ? [inputRules.requiredHideMessage, inputRules.email] : [inputRules.email]"
     />
 
-    <div class="col-12 row q-py-lg">
-      <div class="col-5 q-my-auto">
-        <q-separator class="q-my-sm bg-grey-4" />
-      </div>
+    <template v-if="smsEnabled">
+      <div class="col-12 q-py-lg row">
+        <div class="col-5 q-my-auto">
+          <q-separator class="bg-grey-4 q-my-sm" />
+        </div>
 
-      <span class="col text-center text-text font12">
-        {{ $t("common.ui.misc.or") }}
-      </span>
+        <span class="col font12 fontmedium text-center text-text">
+          {{ $t("common.ui.misc.or") }}
+        </span>
 
-      <div class="col-5 q-my-auto">
-        <q-separator class="q-my-sm bg-grey-4" />
+        <div class="col-5 q-my-auto">
+          <q-separator class="bg-grey-4 q-my-sm" />
+        </div>
       </div>
-    </div>
 
-    <div class="text-text">
-      <span class="font12 fontbold">{{ $t("common.terms.phone") }}</span>
+      <div class="text-text">
+        <span class="font12 fontbold">
+          {{ $t("common.terms.phone") }}
+        </span>
 
-      <span class="font12 fontregular">
-        {{ " (" + $t("common.terms.celular") + ")" }}
-      </span>
-    </div>
+        <span class="font12 fontlight">
+          {{ ` (${$t("common.terms.celular")})` }}
+        </span>
+      </div>
 
-    <q-input
-      v-model="phone"
-      class="bg-surface q-mt-sm"
-      hide-bottom-space
-      input-class="text-text font12 fontregular"
-      lazy-rules
-      mask="(##) #####-####"
-      no-error-icon
-      :bottom-slots="false"
-      :placeholder="$t('common.terms.phone')"
-      :rules="!email ? [inputRules.requiredHideMessage, inputRules.email] : []"
-    />
+      <q-input
+        v-model="phone"
+        class="bg-surface q-mt-sm"
+        hide-bottom-space
+        input-class="text-text"
+        lazy-rules
+        mask="(##) #####-####"
+        no-error-icon
+        :bottom-slots="false"
+        :placeholder="$t('common.terms.phone')"
+        :rules="!email ? [inputRules.requiredHideMessage, inputRules.phone] : [inputRules.phone]"
+      />
+    </template>
   </q-card-section>
 </template>
 
@@ -68,5 +74,12 @@ const phone = defineModel("phone", {
   type: String,
 });
 
+defineProps({
+  smsEnabled: {
+    type: Boolean,
+    default: false,
+  },
+});
+
 const { inputRules } = useInputRules();
 </script>

+ 68 - 18
src/components/login/LoginStep3Panel.vue

@@ -2,7 +2,9 @@
   <q-card-section class="no-padding">
     <div>
       <div class="text-text">
-        <span class="font14 fontbold">{{ $t("auth.full_name") }}</span>
+        <span class="font14 fontbold">
+          {{ $t("auth.full_name") }}
+        </span>
       </div>
 
       <q-input
@@ -22,7 +24,9 @@
 
     <div>
       <div class="text-text">
-        <span class="font14 fontbold">{{ $t("common.terms.document") }}</span>
+        <span class="font14 fontbold">
+          {{ $t("common.terms.document") }}
+        </span>
       </div>
 
       <q-input
@@ -42,7 +46,32 @@
 
     <div>
       <div class="text-text">
-        <span class="font14 fontbold">{{ $t("common.terms.cep") }}</span>
+        <span class="font14 fontbold">
+          {{ $t("common.terms.phone") }}
+        </span>
+      </div>
+
+      <q-input
+        v-model="form.phone"
+        bg-color="surface"
+        class="q-mt-sm q-mb-md"
+        hide-bottom-space
+        input-class="text-text"
+        lazy-rules
+        mask="(##) #####-####"
+        no-error-icon
+        outlined
+        rounded
+        :placeholder="$t('common.terms.phone')"
+        :rules="[inputRules.required, inputRules.phone]"
+      />
+    </div>
+
+    <div>
+      <div class="text-text">
+        <span class="font14 fontbold">
+          {{ $t("common.terms.cep") }}
+        </span>
       </div>
 
       <q-input
@@ -75,7 +104,9 @@
 
     <div>
       <div class="text-text">
-        <span class="font14 fontbold">{{ $t("common.terms.address") }}</span>
+        <span class="font14 fontbold">
+          {{ $t("common.terms.address") }}
+        </span>
       </div>
 
       <q-input
@@ -98,7 +129,8 @@
       <div class="col-4">
         <div class="text-text">
           <span class="font14 fontbold">
-            {{ $t("common.terms.address_number") }}</span>
+            {{ $t("common.terms.address_number") }}
+          </span>
         </div>
 
         <q-input
@@ -118,7 +150,9 @@
 
       <div class="col-8">
         <div class="text-text">
-          <span class="font14 fontbold">{{ $t("common.terms.district") }}</span>
+          <span class="font14 fontbold">
+            {{ $t("common.terms.district") }}
+          </span>
         </div>
 
         <q-input
@@ -127,11 +161,13 @@
           class="q-mt-sm q-mb-md"
           hide-bottom-space
           input-class="text-text"
+          lazy-rules
           no-error-icon
           outlined
           readonly
           rounded
           :placeholder="`${$t('common.terms.district')}...`"
+          :rules="[inputRules.required]"
         />
       </div>
     </div>
@@ -139,24 +175,31 @@
     <div class="row q-col-gutter-sm">
       <div class="col-8">
         <div class="text-text">
-          <span class="font14 fontbold">{{ $t("common.terms.city") }}</span>
+          <span class="font14 fontbold">
+            {{ $t("common.terms.city") }}
+          </span>
         </div>
 
         <q-input
           v-model="form.city"
           bg-color="surface"
           class="q-mt-sm q-mb-md"
+          hide-bottom-space
           input-class="text-text"
+          lazy-rules
           no-error-icon
           outlined
           readonly
           rounded
+          :rules="[inputRules.required]"
         />
       </div>
 
       <div class="col-4">
         <div class="text-text">
-          <span class="font14 fontbold">{{ $t("common.terms.state") }}</span>
+          <span class="font14 fontbold">
+            {{ $t("common.terms.state") }}
+          </span>
         </div>
 
         <q-input
@@ -165,10 +208,12 @@
           class="q-mt-sm q-mb-md"
           hide-bottom-space
           input-class="text-text"
+          lazy-rules
           no-error-icon
           outlined
           readonly
           rounded
+          :rules="[inputRules.required]"
         />
       </div>
     </div>
@@ -187,7 +232,8 @@
       <template v-if="!form.no_complement">
         <div class="text-text">
           <span class="font14 fontbold">
-            {{ $t("common.terms.complement") }}</span>
+            {{ $t("common.terms.complement") }}
+          </span>
         </div>
 
         <q-input
@@ -208,7 +254,9 @@
 
     <div>
       <div class="text-text">
-        <span class="font14 fontbold">{{ $t("auth.address_nickname") }}</span>
+        <span class="font14 fontbold">
+          {{ $t("auth.address_nickname") }}
+        </span>
       </div>
 
       <q-input
@@ -253,13 +301,13 @@
         <q-chip
           v-for="type in addressTypes"
           :key="type.value"
+          :icon="type.icon"
+          :icon-selected="type.icon"
+          :outline="form.address_type !== type.value"
           :selected="form.address_type === type.value"
           clickable
           color="primary"
           text-color="surface"
-          :icon="type.icon"
-          :icon-selected="type.icon"
-          :outline="form.address_type !== type.value"
           @click="form.address_type = type.value"
         >
           {{ $t(type.label) }}
@@ -271,12 +319,13 @@
 
 <script setup>
 import axios from "axios";
-import { useQuasar } from "quasar";
+
 import { ref } from "vue";
+import { useGeolocation } from "src/composables/useGeolocation";
 import { useI18n } from "vue-i18n";
-
 import { useInputRules } from "src/composables/useInputRules";
-import { useGeolocation } from "src/composables/useGeolocation";
+import { useQuasar } from "quasar";
+
 import LocationMapDialog from "src/components/shared/LocationMapDialog.vue";
 
 const form = defineModel({
@@ -284,10 +333,11 @@ const form = defineModel({
   type: Object,
 });
 
+const { requestPermission, getCurrentPosition } = useGeolocation();
 const { inputRules } = useInputRules();
-const $q = useQuasar();
 const { t } = useI18n();
-const { requestPermission, getCurrentPosition } = useGeolocation();
+
+const $q = useQuasar();
 
 const loadingCep = ref(false);
 const loadingLocation = ref(false);

+ 339 - 133
src/components/profile/ProfileFavoritesDialog.vue

@@ -1,72 +1,157 @@
 <!-- eslint-disable @intlify/vue-i18n/no-raw-text -->
 <template>
-  <q-dialog ref="dialogRef" persistent maximized transition-show="slide-left" transition-hide="slide-right">
+  <q-dialog
+    ref="dialogRef"
+    maximized
+    persistent
+    transition-hide="slide-right"
+    transition-show="slide-left"
+  >
     <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
+          color="primary"
+          dense
+          flat
+          icon="mdi-chevron-left"
+          round
+        />
 
-      <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-space />
-        <span class="text-subtitle1 text-primary font16 fontbold gradient-diarista">{{ $t('profile.favorites.title') }}</span>
+
+        <span
+          class="text-subtitle1 text-primary font16 fontbold gradient-diarista"
+        >
+          {{ $t("profile.favorites.title") }}
+        </span>
+
         <q-space />
-        <div style="width: 32px"></div>
+
+        <div style="width: 32px" />
       </div>
 
-      <div v-if="loading" class="col flex flex-center">
-        <q-spinner color="primary" size="3em" />
+      <div
+        v-if="loading"
+        class="col flex flex-center"
+      >
+        <q-spinner
+          color="primary"
+          size="3em"
+        />
       </div>
 
-      <div v-else-if="favorites.length === 0" class="col column items-center justify-center q-px-xl q-pb-xl">
+      <div
+        v-else-if="favorites.length === 0"
+        class="col column items-center justify-center q-px-xl q-pb-xl"
+      >
         <q-img
-          :src="diarinho"
-          style="width: 220px; height: 220px;"
-          fit="contain"
           class="q-mb-lg"
+          fit="contain"
+          style="width: 220px; height: 220px"
+          :src="diarinho"
         />
-        <p class="text-text text-center q-mb-xs" >
-          {{ $t('profile.favorites.empty_message') }}
+
+        <p class="text-text text-center q-mb-xs">
+          {{ $t("profile.favorites.empty_message") }}
         </p>
-        <p class="text-primary text-center  q-mb-xl">
-          {{ $t('profile.favorites.empty_cta') }}
+
+        <p class="text-primary text-center q-mb-xl">
+          {{ $t("profile.favorites.empty_cta") }}
         </p>
+
         <q-btn
           v-close-popup
-          unelevated
-          rounded
-          no-caps
-          color="primary"
           class="full-width q-py-sm"
+          color="primary"
+          no-caps
           padding="10px 16px"
+          rounded
+          unelevated
           :label="$t('profile.favorites.search_btn')"
           @click="goToSearch"
         />
       </div>
 
-      <div v-else class="col overflow-auto q-pb-xl">
-
+      <div
+        v-else
+        class="col overflow-auto q-pb-xl"
+      >
         <div class="q-mx-md q-mt-md q-pa-md">
           <p class="gradient-diarista text-center q-mb-md font12 fontbold">
-            {{ $t('profile.favorites.indicate_title') }}
+            {{ $t("profile.favorites.indicate_title") }}
           </p>
+
           <div class="row items-center">
             <div class="col column items-center">
-              <q-avatar :style="avatarStyle(highlightedFavorite)" size="72px" class=" text-h5 q-mb-sm shadow-1">
-                {{ highlightedFavorite?.provider_name?.charAt(0)?.toUpperCase() ?? '?' }}
+              <q-avatar
+                class="text-h5 q-mb-sm shadow-1"
+                size="72px"
+                :style="avatarStyle(highlightedFavorite)"
+              >
+                <img
+                  v-if="getProfileMediaUrl(highlightedFavorite)"
+                  class="profile-image"
+                  :src="getProfileMediaUrl(highlightedFavorite)"
+                />
+
+                <span v-else>
+                  {{
+                    highlightedFavorite?.provider_name
+                      ?.charAt(0)
+                      ?.toUpperCase() ?? "?"
+                  }}
+                </span>
               </q-avatar>
-              <span class="font14 fontbold text-dark" >{{ highlightedFavorite?.provider_name ?? '—' }}</span>
-              <span class="font12 fontmedium text-grey-6">{{ highlightedFavorite?.city_name ?? '—' }}</span>
+
+              <span class="font14 fontbold text-dark">
+                {{ highlightedFavorite?.provider_name ?? "—" }}
+              </span>
+
+              <span
+                v-if="getProviderGenderLabel(highlightedFavorite, t)"
+                class="font10 fontmedium text-grey-6"
+              >
+                {{ getProviderGenderLabel(highlightedFavorite, t) }}
+              </span>
+
+              <span class="font12 fontmedium text-grey-6">
+                {{ highlightedFavorite?.city_name ?? "—" }}
+              </span>
+
               <div class="row items-center q-gutter-sm q-mt-sm">
-                <q-btn flat round dense icon="mdi-chevron-left" color="primary" :disable="favorites.length <= 1" @click="prevHighlight" />
                 <q-btn
-                  rounded
-                  unelevated
-                  no-caps
                   color="primary"
+                  dense
+                  flat
+                  icon="mdi-chevron-left"
+                  round
+                  :disable="favorites.length <= 1"
+                  @click="prevHighlight"
+                />
+
+                <q-btn
+                  color="primary"
+                  no-caps
                   padding="4px 20px"
+                  rounded
                   size="sm"
+                  unelevated
                   :label="$t('profile.favorites.indicate_btn')"
                   @click="openIndicateDialog(highlightedFavorite)"
                 />
-                <q-btn flat round dense icon="mdi-chevron-right" color="primary" :disable="favorites.length <= 1" @click="nextHighlight" />
+
+                <q-btn
+                  color="primary"
+                  dense
+                  flat
+                  icon="mdi-chevron-right"
+                  round
+                  :disable="favorites.length <= 1"
+                  @click="nextHighlight"
+                />
               </div>
             </div>
           </div>
@@ -74,23 +159,31 @@
 
         <div class="q-mx-md q-mt-lg">
           <div class="row items-center justify-between q-mb-md">
-            <span class="font16 fontbold gradient-diarista">{{ $t('profile.favorites.providers_title') }}</span>
+            <span class="font16 fontbold gradient-diarista">
+              {{ $t("profile.favorites.providers_title") }}
+            </span>
+
             <div class="row items-center">
               <q-icon
+                :class="selectedHours === '2h' ? '' : 'cursor-pointer'"
+                :color="selectedHours === '2h' ? 'grey-4' : 'grey-6'"
                 name="mdi-chevron-left"
                 size="20px"
-                :color="selectedHours === '2h' ? 'grey-4' : 'grey-6'"
-                :class="selectedHours === '2h' ? '' : 'cursor-pointer'"
                 @click="nextSelectedHours"
               />
-              <span class="font12 fontbold text-grey-6 q-px-xs" style="user-select: none;">
-                {{ $t('profile.favorites.until') }} {{ selectedHours }}
+
+              <span
+                class="font12 fontbold text-grey-6 q-px-xs"
+                style="user-select: none"
+              >
+                {{ $t("profile.favorites.until") }} {{ selectedHours }}
               </span>
+
               <q-icon
+                :class="selectedHours === '8h' ? '' : 'cursor-pointer'"
+                :color="selectedHours === '8h' ? 'grey-4' : 'grey-6'"
                 name="mdi-chevron-right"
                 size="20px"
-                :color="selectedHours === '8h' ? 'grey-4' : 'grey-6'"
-                :class="selectedHours === '8h' ? '' : 'cursor-pointer'"
                 @click="prevSelectedHours"
               />
             </div>
@@ -99,57 +192,140 @@
           <div
             v-for="(item, index) in favorites"
             :key="item.id"
-            class="favorite-item row items-center no-wrap q-py-md"
             :class="{ 'item-separator': index < favorites.length - 1 }"
+            class="favorite-item row items-center no-wrap q-py-md"
           >
-            <div class="relative-position q-mr-md" style="margin-right: 16px;">
-              <q-avatar :style="avatarStyle(item)" size="60px" class="shadow-1">
-                {{ item.provider_name?.charAt(0)?.toUpperCase() ?? '?' }}
+            <div
+              class="relative-position q-mr-md"
+              style="margin-right: 16px"
+            >
+              <q-avatar
+                class="shadow-1"
+                size="60px"
+                :style="avatarStyle(item)"
+              >
+                <img
+                  v-if="getProfileMediaUrl(item)"
+                  class="profile-image"
+                  :src="getProfileMediaUrl(item)"
+                />
+
+                <span v-else>
+                  {{ item.provider_name?.charAt(0)?.toUpperCase() ?? "?" }}
+                </span>
               </q-avatar>
+
               <q-icon
+                class="absolute cursor-pointer"
                 name="mdi-heart"
                 size="26px"
-                class="absolute cursor-pointer"
-                style="bottom: -4px; right: -4px; color: #f518e3; filter: drop-shadow(0px 1px 2px rgba(0,0,0,0.2));"
+                style="bottom: -4px; right: -4px; color: #f518e3; filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.2))"
                 @click.stop="confirmRemove(item)"
               />
             </div>
 
-            <div class="col column justify-between" style="min-height: 60px;">
+            <div
+              class="col column justify-between"
+              style="min-height: 60px"
+            >
               <div class="row items-start justify-between">
                 <div class="column">
-                  <span class="provider-name font12 fontmedium" style="color: #4a4a4a; line-height: 1.1;">
-                    {{ item.provider_name ?? '—' }}
+                  <span
+                    class="provider-name font12 fontmedium"
+                    style="color: #4a4a4a; line-height: 1.1"
+                  >
+                    {{ item.provider_name ?? "—" }}
                   </span>
-                  <span class="provider-city font9 fontbold text-grey-7 q-pt-xs">
-                    {{ item.city_name ?? '—' }}
+
+                  <span
+                    v-if="getProviderGenderLabel(item, t)"
+                    class="font9 fontmedium text-grey-7 q-pt-xs"
+                  >
+                    {{ getProviderGenderLabel(item, t) }}
+                  </span>
+
+                  <span
+                    class="provider-city font9 fontbold text-grey-7 q-pt-xs"
+                  >
+                    {{ item.city_name ?? "—" }}
                   </span>
                 </div>
-                <span class="provider-price font12 fontregular text-grey-7" style="line-height: 1.1;">
-                  R${{ getPriceByHours(item) ? formatPrice(getPriceByHours(item)) : '—' }}
+
+                <span
+                  class="provider-price font12 fontregular text-grey-7"
+                  style="line-height: 1.1"
+                >
+                  R${{
+                    getPriceByHours(item)
+                      ? formatPrice(getPriceByHours(item))
+                      : "—"
+                  }}
                 </span>
               </div>
 
               <div class="row items-end justify-between q-mt-xs">
-                <div class="row items-center text-grey-5" style="margin-bottom: 2px;">
-                  <q-icon name="mdi-star" color="warning" size="12px" />
-                  <span class="q-ml-xs font9 fontregular" style="color: #666;">{{ item.average_rating ?? '—' }}</span>
-                  <span class="q-ml-xs font9 fontregular" style="margin-right: 2px;">({{ item.total_services ?? 0 }})</span>
-                  <q-icon name="mdi-circle-small" color="grey-4" size="12px" class="q-mx-xs" />
-                  <q-icon name="mdi-broom" size="12px" style="transform: scaleX(-1); color: #f518e3;" />
-                  <span class="q-ml-xs font9 fontregular" style="color: #666;">{{ item.total_services ?? '0' }}</span>
-                  <q-icon name="mdi-circle-small" color="grey-4" size="16px" class="q-mx-xs" />
-                  <span class="font9 fontregular">0Km</span>
+                <div
+                  class="row items-center text-grey-5"
+                  style="margin-bottom: 2px"
+                >
+                  <q-icon
+                    color="warning"
+                    name="mdi-star"
+                    size="12px"
+                  />
+
+                  <span
+                    class="q-ml-xs font9 fontregular"
+                    style="color: #666"
+                  >
+                    {{ item.average_rating ?? "—" }}
+                  </span>
+
+                  <span
+                    class="q-ml-xs font9 fontregular"
+                    style="margin-right: 2px"
+                  >
+                    ({{ item.total_services ?? 0 }})
+                  </span>
+
+                  <q-icon
+                    class="q-mx-xs"
+                    color="grey-4"
+                    name="mdi-circle-small"
+                    size="12px"
+                  />
+
+                  <q-icon
+                    name="mdi-broom"
+                    size="12px"
+                    style="color: #f518e3; transform: scaleX(-1)"
+                  />
+
+                  <span
+                    class="q-ml-xs font9 fontregular"
+                    style="color: #666"
+                  >
+                    {{ item.total_services ?? "0" }}
+                  </span>
+
+                  <q-icon
+                    class="q-mx-xs"
+                    color="grey-4"
+                    name="mdi-circle-small"
+                    size="16px"
+                  />
+
+                  <span class="font9 fontregular"> 0Km </span>
                 </div>
-                
+
                 <q-btn
-                  rounded
-                  unelevated
+                  class=""
                   no-caps
-                  style="background: #a63df7; color: white;"
                   padding="4px 20px"
+                  rounded
                   size="sm"
-                  class=""
+                  style="background: #a63df7; color: white"
+                  unelevated
                   :label="$t('profile.favorites.schedule_btn')"
                 />
               </div>
@@ -157,124 +333,149 @@
           </div>
         </div>
       </div>
-
     </div>
   </q-dialog>
 </template>
 
 <script setup>
-import { ref, computed, onMounted } from 'vue';
-import { useDialogPluginComponent, useQuasar } from 'quasar';
-import { useI18n } from 'vue-i18n';
-import { userStore } from 'src/stores/user';
-import { useRouter } from 'vue-router';
-import { getClientFavoriteProviders, deleteClientFavoriteProvider } from 'src/api/clientFavoriteProvider';
-import ProfileFavoriteRemoveDialog from './ProfileFavoriteRemoveDialog.vue';
-import diarinho from 'src/assets/diarinho_perfil_cliente_favoritos.svg';
+import diarinho from "src/assets/diarinho_perfil_cliente_favoritos.svg";
+
+import { avatarColorsWithPurple as avatarColors } from "src/helpers/avatarColors";
+
+import { computed, onMounted, ref } from "vue";
+
+import {
+  deleteClientFavoriteProvider,
+  getClientFavoriteProviders,
+} from "src/api/clientFavoriteProvider";
+
+import { getProfileMediaUrl } from "src/helpers/profileMedia";
+import { getProviderGenderLabel } from "src/helpers/provider";
+import { useDialogPluginComponent, useQuasar } from "quasar";
+import { useI18n } from "vue-i18n";
+import { useRouter } from "vue-router";
+import { userStore } from "src/stores/user";
+
+import ProfileFavoriteRemoveDialog from "./ProfileFavoriteRemoveDialog.vue";
 
 defineEmits([...useDialogPluginComponent.emits]);
 
 const { dialogRef } = useDialogPluginComponent();
-const $q = useQuasar();
+
 const { t } = useI18n();
-const store = userStore();
 
+const $q = useQuasar();
 const router = useRouter();
+const store = userStore();
+
 const favorites = ref([]);
-const loading = ref(false);
 const highlightIndex = ref(0);
-const selectedHours = ref('8h');
+const loading = ref(false);
+const selectedHours = ref("8h");
 
-const hoursOptions = ['8h', '6h', '4h', '2h'];
+const highlightedFavorite = computed(
+  () => favorites.value[highlightIndex.value] ?? null,
+);
 
-const prevSelectedHours = () => {
-  const currentIndex = hoursOptions.indexOf(selectedHours.value);
-  if (currentIndex > 0) {
-    selectedHours.value = hoursOptions[currentIndex - 1];
-  }
-};
+const hoursOptions = ["8h", "6h", "4h", "2h"];
 
-const nextSelectedHours = () => {
-  const currentIndex = hoursOptions.indexOf(selectedHours.value);
-  if (currentIndex < hoursOptions.length - 1) {
-    selectedHours.value = hoursOptions[currentIndex + 1];
-  }
+const avatarStyle = (item) => {
+  const index = (item?.id ?? 0) % avatarColors.length;
+
+  return avatarColors[index];
 };
 
-const avatarColors = [
-  { background: '#ffd5df', color: '#932e57' },
-  { background: '#d7e8ff', color: '#2158a8' },
-  { background: '#dfd',    color: '#2a7a3b' },
-  { background: '#ffe5cc', color: '#8a4500' },
-  { background: '#ede0ff', color: '#6200ea' },
-];
+const confirmRemove = (item) => {
+  $q.dialog({
+    component: ProfileFavoriteRemoveDialog,
+  }).onOk(async () => {
+    try {
+      await deleteClientFavoriteProvider(item.id);
 
-const avatarStyle = (item) => {
-  const idx = (item?.id ?? 0) % avatarColors.length;
-  return avatarColors[idx];
+      favorites.value = favorites.value.filter(
+        (favorite) => favorite.id !== item.id,
+      );
+
+      if (highlightIndex.value >= favorites.value.length) {
+        highlightIndex.value = Math.max(0, favorites.value.length - 1);
+      }
+    } catch (error) {
+      console.error("Erro ao remover favorito:", error);
+    }
+  });
 };
 
 const formatPrice = (value) => {
-  return Number(value).toLocaleString('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
+  return Number(value).toLocaleString("pt-BR", {
+    maximumFractionDigits: 2,
+    minimumFractionDigits: 2,
+  });
 };
 
 const getPriceByHours = (item) => {
   const priceKey = `daily_price_${selectedHours.value}`;
+
   return item[priceKey] ?? item.daily_price_8h ?? 0;
 };
 
-const highlightedFavorite = computed(() => favorites.value[highlightIndex.value] ?? null);
-
-const prevHighlight = () => {
-  highlightIndex.value = highlightIndex.value === 0
-    ? favorites.value.length - 1
-    : highlightIndex.value - 1;
+const goToSearch = () => {
+  router.push({
+    name: "SearchPage",
+  });
 };
 
 const nextHighlight = () => {
   highlightIndex.value = (highlightIndex.value + 1) % favorites.value.length;
 };
 
-const goToSearch = () => {
-  router.push({
-    name: 'SearchPage'
-  });
-};
+const nextSelectedHours = () => {
+  const currentIndex = hoursOptions.indexOf(selectedHours.value);
 
-const confirmRemove = (item) => {
-  $q.dialog({
-    component: ProfileFavoriteRemoveDialog,
-  }).onOk(async () => {
-    try {
-      await deleteClientFavoriteProvider(item.id);
-      favorites.value = favorites.value.filter(f => f.id !== item.id);
-      if (highlightIndex.value >= favorites.value.length) {
-        highlightIndex.value = Math.max(0, favorites.value.length - 1);
-      }
-    } catch (error) {
-      console.error('Erro ao remover favorito:', error);
-    }
-  });
+  if (currentIndex < hoursOptions.length - 1) {
+    selectedHours.value = hoursOptions[currentIndex + 1];
+  }
 };
 
 const openIndicateDialog = (item) => {
-  const message = t('profile.favorites.indicate_whatsapp_message', {
-    name: item?.provider_name ?? '',
-    city: item?.city_name ?? '',
+  const message = t("profile.favorites.indicate_whatsapp_message", {
+    city: item?.city_name ?? "",
+    name: item?.provider_name ?? "",
+  });
+
+  console.log("[Indicar diarista - WhatsApp]", message);
+
+  $q.notify({
+    message: t("profile.favorites.indicate_coming_soon"),
+    type: "info",
   });
-  console.log('[Indicar diarista - WhatsApp]', message);
-  $q.notify({ type: 'info', message: t('profile.favorites.indicate_coming_soon') });
+};
+
+const prevHighlight = () => {
+  highlightIndex.value =
+    highlightIndex.value === 0
+      ? favorites.value.length - 1
+      : highlightIndex.value - 1;
+};
+
+const prevSelectedHours = () => {
+  const currentIndex = hoursOptions.indexOf(selectedHours.value);
+
+  if (currentIndex > 0) {
+    selectedHours.value = hoursOptions[currentIndex - 1];
+  }
 };
 
 onMounted(async () => {
   loading.value = true;
+
   try {
     const clientId = store.user?.client_id;
+
     if (clientId) {
       favorites.value = await getClientFavoriteProviders(clientId);
     }
   } catch (error) {
-    console.error('Erro ao carregar favoritos:', error);
+    console.error("Erro ao carregar favoritos:", error);
   } finally {
     loading.value = false;
   }
@@ -282,7 +483,6 @@ onMounted(async () => {
 </script>
 
 <style scoped lang="scss">
-
 .heart-badge {
   position: absolute;
   bottom: -2px;
@@ -334,6 +534,12 @@ onMounted(async () => {
   color: #2c3e50;
 }
 
+.profile-image {
+  height: 100%;
+  object-fit: cover;
+  width: 100%;
+}
+
 .shadow-profile {
   box-shadow: 0px 1px 8px rgba(0, 0, 0, 0.1);
 }

+ 122 - 62
src/components/profile/ProfilePrivacyDialog.vue

@@ -1,38 +1,74 @@
 <template>
-  <q-dialog ref="dialogRef" persistent maximized transition-show="slide-left" transition-hide="slide-right">
+  <q-dialog
+    ref="dialogRef"
+    maximized
+    persistent
+    transition-hide="slide-right"
+    transition-show="slide-left"
+  >
     <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
+          color="primary"
+          dense
+          flat
+          icon="mdi-chevron-left"
+          round
+        />
 
-      <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-space />
-        <span class="text-subtitle1 text-primary font16 fontbold gradient-diarista">{{ $t('profile.privacy.title') }}</span>
+
+        <span
+          class="text-subtitle1 text-primary font16 fontbold gradient-diarista"
+        >
+          {{ $t("profile.privacy.title") }}
+        </span>
+
         <q-space />
+
         <div style="width: 32px"></div>
       </div>
 
-      <div v-if="loading" class="col flex flex-center">
-        <q-spinner color="primary" size="3em" />
+      <div
+        v-if="loading"
+        class="col flex flex-center"
+      >
+        <q-spinner
+          color="primary"
+          size="3em"
+        />
       </div>
 
-      <div v-else-if="blocks.length === 0" class="col column items-center justify-center q-px-xl q-pb-xl">
+      <div
+        v-else-if="blocks.length === 0"
+        class="col column items-center justify-center q-px-xl q-pb-xl"
+      >
         <q-img
-          :src="diarinho"
-          style="width: 220px; height: 220px;"
-          fit="contain"
           class="q-mb-lg"
+          fit="contain"
+          style="width: 220px; height: 220px"
+          :src="diarinho"
         />
+
         <p class="text-center q-mb-xs font16 fontbold text-primary">
-          {{ $t('profile.privacy.empty_message') }}
+          {{ $t("profile.privacy.empty_message") }}
         </p>
+
         <p class="text-grey-6 text-center q-mb-xl font12 fontregular">
-          {{ $t('profile.privacy.empty_sub') }}
+          {{ $t("profile.privacy.empty_sub") }}
         </p>
       </div>
 
-      <div v-else class="col overflow-auto q-pb-xl">
+      <div
+        v-else
+        class="col overflow-auto q-pb-xl"
+      >
         <div class="q-px-md q-mt-md">
           <p class="text-primary q-mb-md font16 fontbold gradient-diarista">
-            {{ $t('profile.privacy.blocked_title') }}
+            {{ $t("profile.privacy.blocked_title") }}
           </p>
 
           <div
@@ -40,94 +76,119 @@
             :key="block.id"
             class="block-item row items-center no-wrap q-mb-md"
           >
-            <q-avatar size="48px" class="flex-shrink-0 q-mr-sm">
+            <q-avatar
+              class="flex-shrink-0 q-mr-sm"
+              size="48px"
+            >
               <span
-                :style="avatarStyle(block)"
                 class="full-width full-height flex flex-center"
-                style="border-radius: 50%;"
+                style="border-radius: 50%"
+                :style="avatarStyle(block)"
               >
-                {{ block.provider_name?.slice(0, 2).toUpperCase() ?? '??' }}
+                {{ block.provider_name?.slice(0, 2).toUpperCase() ?? "??" }}
               </span>
             </q-avatar>
 
             <div class="col column no-wrap overflow-hidden">
-              <span class="font12 fontbold text-text" style="line-height: 1.3;">
+              <span
+                class="font12 fontbold text-text"
+                style="line-height: 1.3"
+              >
                 {{ block.provider_name }}
               </span>
-              <span v-if="block.provider_district" class="text-grey-7 font9 fontbold" >
+
+              <span
+                v-if="block.provider_district"
+                class="text-grey-7 font9 fontbold"
+              >
                 {{ block.provider_district }}
               </span>
-              <div v-if="block.provider_rating" class="row items-center q-mt-xs">
-                <q-icon name="mdi-star" color="amber" size="12px" class="q-mr-xs" />
-                <span class="text-grey-7 font9 fontregular">{{ Number(block.provider_rating).toFixed(1) }}</span>
+
+              <div
+                v-if="block.provider_rating"
+                class="row items-center q-mt-xs"
+              >
+                <q-icon
+                  class="q-mr-xs"
+                  color="amber"
+                  name="mdi-star"
+                  size="12px"
+                />
+
+                <span class="text-grey-7 font9 fontregular">
+                  {{ Number(block.provider_rating).toFixed(1) }}
+                </span>
               </div>
             </div>
 
             <q-btn
+              class="flex-shrink-0"
+              color="primary"
               no-caps
+              padding="4px 12px"
               rounded
-              color="primary"
               size="sm"
-              class="flex-shrink-0"
-              padding="4px 12px"
-              :loading="unblockingId === block.id"
               :label="$t('profile.privacy.unblock_btn')"
+              :loading="unblockingId === block.id"
               @click="unblock(block)"
             />
           </div>
         </div>
       </div>
-
     </div>
   </q-dialog>
 </template>
 
 <script setup>
-import { ref, onMounted } from 'vue'
-import { useDialogPluginComponent } from 'quasar'
-import { userStore } from 'src/stores/user'
-import { getClientProviderBlocks, deleteClientProviderBlock } from 'src/api/clientProviderBlock'
-import diarinho from 'src/assets/diarinho_perfil_cliente_favoritos.svg'
-
-defineEmits([...useDialogPluginComponent.emits])
-const { dialogRef } = useDialogPluginComponent()
-const store = userStore()
-
-const blocks = ref([])
-const loading = ref(false)
-const unblockingId = ref(null)
-
-const avatarColors = [
-  { background: '#ffd5df', color: '#932e57' },
-  { background: '#d7e8ff', color: '#2158a8' },
-  { background: '#dfd',    color: '#2a7a3b' },
-  { background: '#ffe5cc', color: '#8a4500' },
-  { background: '#ede0ff', color: '#6200ea' },
-]
+import diarinho from "src/assets/diarinho_perfil_cliente_favoritos.svg";
+
+import { avatarColorsWithPurple as avatarColors } from "src/helpers/avatarColors";
+
+import {
+  deleteClientProviderBlock,
+  getClientProviderBlocks,
+} from "src/api/clientProviderBlock";
+
+import { onMounted, ref } from "vue";
+import { useDialogPluginComponent } from "quasar";
+import { userStore } from "src/stores/user";
+
+defineEmits([...useDialogPluginComponent.emits]);
+
+const { dialogRef } = useDialogPluginComponent();
+const store = userStore();
+
+const blocks = ref([]);
+const loading = ref(false);
+const unblockingId = ref(null);
 
 const avatarStyle = (block) => {
-  const idx = (block?.provider_id ?? 0) % avatarColors.length
-  return avatarColors[idx]
-}
+  const idx = (block?.provider_id ?? 0) % avatarColors.length;
+
+  return avatarColors[idx];
+};
 
 const unblock = async (block) => {
-  unblockingId.value = block.id
+  unblockingId.value = block.id;
+
   try {
-    await deleteClientProviderBlock(block.id)
-    blocks.value = blocks.value.filter(b => b.id !== block.id)
+    await deleteClientProviderBlock(block.id);
+
+    blocks.value = blocks.value.filter((b) => b.id !== block.id);
   } finally {
-    unblockingId.value = null
+    unblockingId.value = null;
   }
-}
+};
 
 onMounted(async () => {
-  loading.value = true
+  loading.value = true;
+
   try {
-    blocks.value = await getClientProviderBlocks(store.user.client.id) ?? []
+    blocks.value = (await getClientProviderBlocks(store.user.client.id)) ?? [];
   } finally {
-    loading.value = false
+    loading.value = false;
   }
-})
+});
 </script>
 
 <style scoped lang="scss">
@@ -140,7 +201,6 @@ onMounted(async () => {
   padding: 12px;
 }
 
-
 .flex-shrink-0 {
   flex-shrink: 0;
 }

+ 83 - 78
src/composables/useInputRules.js

@@ -1,18 +1,91 @@
 import { useI18n } from "vue-i18n";
 
+const isValidCNPJ = (cnpj) => {
+  if (!cnpj) return false;
+
+  cnpj = cnpj.replace(/[^\d]+/g, "");
+
+  if (cnpj.length !== 14) return false;
+  if (/^(\d)\1+$/.test(cnpj)) return false;
+
+  let length = cnpj.length - 2;
+  let numbers = cnpj.substring(0, length);
+  let digits = cnpj.substring(length);
+  let sum = 0;
+  let pos = length - 7;
+
+  for (let i = length; i >= 1; i--) {
+    sum += parseInt(numbers.charAt(length - i)) * pos--;
+
+    if (pos < 2) pos = 9;
+  }
+
+  let result = sum % 11 < 2 ? 0 : 11 - (sum % 11);
+
+  if (result !== parseInt(digits.charAt(0))) return false;
+
+  length = length + 1;
+  numbers = cnpj.substring(0, length);
+  sum = 0;
+  pos = length - 7;
+
+  for (let i = length; i >= 1; i--) {
+    sum += parseInt(numbers.charAt(length - i)) * pos--;
+
+    if (pos < 2) pos = 9;
+  }
+
+  result = sum % 11 < 2 ? 0 : 11 - (sum % 11);
+
+  if (result !== parseInt(digits.charAt(1))) return false;
+
+  return true;
+};
+
+const isValidCPF = (cpf) => {
+  if (!cpf) return false;
+
+  cpf = cpf.replace(/[^\d]+/g, "");
+
+  if (cpf.length !== 11) return false;
+  if (/^(\d)\1+$/.test(cpf)) return false;
+
+  let sum = 0;
+
+  for (let i = 0; i < 9; i++) sum += parseInt(cpf.charAt(i)) * (10 - i);
+
+  let rev = 11 - (sum % 11);
+
+  if (rev === 10 || rev === 11) rev = 0;
+
+  if (rev !== parseInt(cpf.charAt(9))) return false;
+
+  sum = 0;
+
+  for (let i = 0; i < 10; i++) sum += parseInt(cpf.charAt(i)) * (11 - i);
+
+  rev = 11 - (sum % 11);
+
+  if (rev === 10 || rev === 11) rev = 0;
+
+  if (rev !== parseInt(cpf.charAt(10))) return false;
+
+  return true;
+};
+
 export const useInputRules = () => {
   const { t } = useI18n();
 
   const emailPattern =
     /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
 
- const passwordPattern = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[a-zA-Z]).{8,}$/;
+  const passwordPattern = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[a-zA-Z]).{8,}$/;
 
- const cepPattern = /^[0-9]{5}-[0-9]{3}$/;
+  const cepPattern = /^[0-9]{5}-[0-9]{3}$/;
 
   const inputRules = {
-    required:            (value) => !!value || t("validation.rules.required"),
-    requiredNumber:      (value) => !isNaN(value) || t("validation.rules.required"),
+    required: (value) => !!value || t("validation.rules.required"),
+    requiredNumber: (value) => !isNaN(value) || t("validation.rules.required"),
     requiredHideMessage: (value) => !!value,
 
     min: (min) => (value) =>
@@ -32,6 +105,11 @@ export const useInputRules = () => {
     email: (value) =>
       !value || emailPattern.test(value) || t("validation.rules.email"),
 
+    phone: (value) =>
+      !value ||
+      String(value).replace(/\D/g, "").length >= 10 ||
+      t("validation.rules.phone"),
+
     emails: (value) => {
       if (!value) return true;
 
@@ -43,7 +121,7 @@ export const useInputRules = () => {
       );
     },
 
-    cpf:  (value) => !value || isValidCPF(value) || t("validation.rules.cpf"),
+    cpf: (value) => !value || isValidCPF(value) || t("validation.rules.cpf"),
     cnpj: (value) => !value || isValidCNPJ(value) || t("validation.rules.cnpj"),
 
     cpfOrCnpj: (value) => {
@@ -93,76 +171,3 @@ export const useInputRules = () => {
     inputRules,
   };
 };
-
-function isValidCPF(cpf) {
-  if (!cpf) return false;
-
-  cpf = cpf.replace(/[^\d]+/g, "");
-
-  if (cpf.length !== 11)     return false;
-  if (/^(\d)\1+$/.test(cpf)) return false;
-
-  let sum = 0;
-
-  for (let i = 0; i < 9; i++) sum += parseInt(cpf.charAt(i)) * (10 - i);
-
-  let rev = 11 - (sum % 11);
-
-  if (rev === 10 || rev === 11) rev = 0;
-
-  if (rev !== parseInt(cpf.charAt(9))) return false;
-
-  sum = 0;
-
-  for (let i = 0; i < 10; i++) sum += parseInt(cpf.charAt(i)) * (11 - i);
-
-  rev = 11 - (sum % 11);
-
-  if (rev === 10 || rev === 11) rev = 0;
-
-  if (rev !== parseInt(cpf.charAt(10))) return false;
-
-  return true;
-}
-
-function isValidCNPJ(cnpj) {
-  if (!cnpj) return false;
-
-  cnpj = cnpj.replace(/[^\d]+/g, "");
-
-  if (cnpj.length !== 14)     return false;
-  if (/^(\d)\1+$/.test(cnpj)) return false;
-
-  let length  = cnpj.length - 2;
-  let numbers = cnpj.substring(0, length);
-  let digits  = cnpj.substring(length);
-  let sum     = 0;
-  let pos     = length - 7;
-
-  for (let i = length; i >= 1; i--) {
-    sum += parseInt(numbers.charAt(length - i)) * pos--;
-
-    if (pos < 2) pos = 9;
-  }
-
-  let result = sum % 11 < 2 ? 0 : 11 - (sum % 11);
-
-  if (result !== parseInt(digits.charAt(0))) return false;
-
-  length  = length + 1;
-  numbers = cnpj.substring(0, length);
-  sum     = 0;
-  pos     = length - 7;
-
-  for (let i = length; i >= 1; i--) {
-    sum += parseInt(numbers.charAt(length - i)) * pos--;
-
-    if (pos < 2) pos = 9;
-  }
-
-  result = sum % 11 < 2 ? 0 : 11 - (sum % 11);
-
-  if (result !== parseInt(digits.charAt(1))) return false;
-
-  return true;
-}

+ 20 - 12
src/i18n/locales/en.json

@@ -4,6 +4,7 @@
     "actions": {
       "save": "Save",
       "cancel": "Cancel",
+      "close": "Close",
       "edit": "Edit",
       "add": "Add",
       "select": "request",
@@ -54,6 +55,7 @@
       "payment_amount": "Payment Amount",
       "language": "Language",
       "currency": "Currency",
+      "gender": "Gender",
       "interests": "Interests",
       "avatar": "Avatar",
       "banner": "Banner",
@@ -188,6 +190,7 @@
     "rules": {
       "required": "This field is required",
       "email": "This field must be a valid email | These fields must be valid emails",
+      "phone": "This field must be a valid phone number",
       "date": "This field must be a valid date",
       "min": "This field must have at least",
       "max": "This field must have at most",
@@ -743,7 +746,7 @@
     "unknown_client": "Client",
     "select_service": "Select service",
     "no_slots_available": "No time slots available for this day.",
-    "book": "add to cart",
+    "book": "add to orders",
     "no_price": "to be agreed",
     "time_selection": {
       "subtitle": "Select the desired start and end time for the service.",
@@ -796,12 +799,12 @@
     }
   },
   "schedule_cart": {
-    "title": "Cart",
-    "empty_title": "Your cart is empty",
+    "title": "Orders",
+    "empty_title": "You do not have any orders yet",
     "empty_message": "Add time slots from favorite professionals to send a request.",
-    "add_to_cart": "Add to cart",
-    "add_success": "Time slot added to cart.",
-    "local_cart_title": "Cart",
+    "add_to_cart": "Add to orders",
+    "add_success": "Time slot added to orders.",
+    "local_cart_title": "Orders",
     "orders_title": "Orders",
     "no_orders": "No orders created.",
     "order_title": "Order #{id}",
@@ -822,17 +825,17 @@
     "credit_card_fee": "Card total",
     "credit_card_total": "Card total",
     "pix_discount": "Pix discount: save R$ {value}",
-    "cart_fee_discount": "Cart total discount: R$ {value}",
-    "cart_discount_hint": "Add {count} more time slot(s) to get the cart discount.",
-    "cart_discount_applied": "Discount applied: 3 or more time slots in the cart.",
+    "cart_fee_discount": "Orders total discount: R$ {value}",
+    "cart_discount_hint": "Add {count} more time slot(s) to get the orders discount.",
+    "cart_discount_applied": "Discount applied: 3 or more time slots in the orders.",
     "cart_discount_total": "Total",
     "provider": "Professional",
     "address": "Address",
     "quantity": "Quantity",
     "schedule_count": "{count} time slot(s)",
     "submit": "send request",
-    "close_cart": "close cart",
-    "cart_closed_success": "Cart closed. Wait for provider approval.",
+    "close_cart": "close orders",
+    "cart_closed_success": "Orders closed. Wait for provider approval.",
     "unknown": "No information",
     "multiple_providers": "{count} professionals",
     "loading": "Loading...",
@@ -895,7 +898,7 @@
     "home": "Home",
     "search": "Search",
     "agenda": "Schedule",
-    "cart": "Cart",
+    "cart": "Orders",
     "profile": "Profile"
   },
   "provider": {
@@ -913,6 +916,11 @@
       }
     }
   },
+  "genders": {
+    "female": "Female",
+    "male": "Male",
+    "not_informed": "Not informed"
+  },
   "support_request": {
     "title": "Talk to an agent",
     "subtitle": "Fill in the fields below and a human agent will get in touch with you.",

+ 20 - 12
src/i18n/locales/es.json

@@ -4,6 +4,7 @@
     "actions": {
       "save": "Guardar",
       "cancel": "Cancelar",
+      "close": "Cerrar",
       "edit": "Editar",
       "add": "Añadir",
       "select": "pedido",
@@ -54,6 +55,7 @@
       "payment_amount": "Monto del pago",
       "language": "Idioma",
       "currency": "Moneda",
+      "gender": "Género",
       "interests": "Intereses",
       "avatar": "Avatar",
       "banner": "Banner",
@@ -188,6 +190,7 @@
     "rules": {
       "required": "Este campo es obligatorio",
       "email": "Este campo debe ser un correo electrónico válido | Estos campos deben ser correos electrónicos válidos",
+      "phone": "Este campo debe ser un número de teléfono válido",
       "date": "Este campo debe ser una fecha válida",
       "min": "Este campo debe tener al menos",
       "max": "Este campo debe tener como máximo",
@@ -739,7 +742,7 @@
     "unknown_client": "Cliente",
     "select_service": "Seleccionar servicio",
     "no_slots_available": "No hay horarios disponibles para este día.",
-    "book": "agregar al carrito",
+    "book": "agregar a pedidos",
     "no_price": "a convenir",
     "time_selection": {
       "subtitle": "Seleccione el horario deseado para inicio y fin del servicio.",
@@ -792,12 +795,12 @@
     }
   },
   "schedule_cart": {
-    "title": "Carrito",
-    "empty_title": "Su carrito está vacío",
+    "title": "Pedidos",
+    "empty_title": "Todavía no tiene pedidos",
     "empty_message": "Agregue horarios de profesionales favoritos para enviar una solicitud.",
-    "add_to_cart": "Agregar al carrito",
-    "add_success": "Horario agregado al carrito.",
-    "local_cart_title": "Carrito",
+    "add_to_cart": "Agregar a pedidos",
+    "add_success": "Horario agregado a pedidos.",
+    "local_cart_title": "Pedidos",
     "orders_title": "Pedidos",
     "no_orders": "Ningún pedido creado.",
     "order_title": "Pedido #{id}",
@@ -818,17 +821,17 @@
     "credit_card_fee": "Total con tarjeta",
     "credit_card_total": "Total con tarjeta",
     "pix_discount": "Descuento con Pix: ahorre R$ {value}",
-    "cart_fee_discount": "Descuento en el total del carrito: R$ {value}",
-    "cart_discount_hint": "Agregue {count} horario(s) más para recibir descuento en el carrito.",
-    "cart_discount_applied": "Descuento aplicado: 3 o más horarios en el carrito.",
+    "cart_fee_discount": "Descuento en el total de los pedidos: R$ {value}",
+    "cart_discount_hint": "Agregue {count} horario(s) más para recibir descuento en los pedidos.",
+    "cart_discount_applied": "Descuento aplicado: 3 o más horarios en los pedidos.",
     "cart_discount_total": "Total",
     "provider": "Profesional",
     "address": "Dirección",
     "quantity": "Cantidad",
     "schedule_count": "{count} horario(s)",
     "submit": "enviar solicitud",
-    "close_cart": "cerrar carrito",
-    "cart_closed_success": "Carrito cerrado. Espere la aprobación de los profesionales.",
+    "close_cart": "cerrar pedidos",
+    "cart_closed_success": "Pedidos cerrados. Espere la aprobación de los profesionales.",
     "unknown": "Sin información",
     "multiple_providers": "{count} profesionales",
     "loading": "Cargando...",
@@ -891,7 +894,7 @@
     "home": "Inicio",
     "search": "Buscar",
     "agenda": "Agenda",
-    "cart": "Carrito",
+    "cart": "Pedidos",
     "profile": "Perfil"
   },
   "provider": {
@@ -909,6 +912,11 @@
       }
     }
   },
+  "genders": {
+    "female": "Femenino",
+    "male": "Masculino",
+    "not_informed": "No informado"
+  },
   "support_request": {
     "title": "Hablar con un agente",
     "subtitle": "Completa los campos a continuación y un agente humano se pondrá en contacto contigo.",

+ 20 - 12
src/i18n/locales/pt.json

@@ -4,6 +4,7 @@
     "actions": {
       "save": "Salvar",
       "cancel": "Cancelar",
+      "close": "Fechar",
       "edit": "Editar",
       "add": "Adicionar",
       "select": "solicitar",
@@ -54,6 +55,7 @@
       "payment_amount": "Valor do Pagamento",
       "language": "Idioma",
       "currency": "Moeda",
+      "gender": "Gênero",
       "interests": "Interesses",
       "avatar": "Avatar",
       "banner": "Banner",
@@ -188,6 +190,7 @@
     "rules": {
       "required": "Este campo é obrigatório",
       "email": "Este campo deve ser um e-mail válido | Estes campos devem ser e-mails válidos",
+      "phone": "Este campo deve ser um telefone válido",
       "date": "Este campo deve ser uma data válida",
       "min": "Este campo deve ter no mínimo",
       "max": "Este campo deve ter no máximo",
@@ -751,7 +754,7 @@
     "unknown_client": "Cliente",
     "select_service": "Selecionar serviço",
     "no_slots_available": "Não há horários disponíveis para este dia.",
-    "book": "carrinho",
+    "book": "pedidos",
     "no_price": "a combinar",
     "time_selection": {
       "subtitle": "Selecione o horário desejado para início e término do serviço.",
@@ -804,12 +807,12 @@
     }
   },
   "schedule_cart": {
-    "title": "Carrinho",
-    "empty_title": "Seu carrinho está vazio",
+    "title": "Pedidos",
+    "empty_title": "Você ainda não tem pedidos",
     "empty_message": "Adicione horários de profissionais favoritos para enviar uma solicitação.",
-    "add_to_cart": "Adicionar ao carrinho",
-    "add_success": "Horário adicionado ao carrinho.",
-    "local_cart_title": "Carrinho",
+    "add_to_cart": "Adicionar aos pedidos",
+    "add_success": "Horário adicionado aos pedidos.",
+    "local_cart_title": "Pedidos",
     "orders_title": "Pedidos",
     "no_orders": "Nenhum pedido criado.",
     "order_title": "Pedido #{id}",
@@ -830,17 +833,17 @@
     "credit_card_fee": "Total no cartão",
     "credit_card_total": "Total no cartão",
     "pix_discount": "Desconto no Pix: economize R$ {value}",
-    "cart_fee_discount": "Desconto no total do carrinho: R$ {value}",
-    "cart_discount_hint": "Adicione mais {count} horário(s) para receber desconto no carrinho.",
-    "cart_discount_applied": "Desconto aplicado: 3 ou mais horários no carrinho.",
+    "cart_fee_discount": "Desconto no total dos pedidos: R$ {value}",
+    "cart_discount_hint": "Adicione mais {count} horário(s) para receber desconto nos pedidos.",
+    "cart_discount_applied": "Desconto aplicado: 3 ou mais horários nos pedidos.",
     "cart_discount_total": "Total",
     "provider": "Profissional",
     "address": "Endereço",
     "quantity": "Quantidade",
     "schedule_count": "{count} horário(s)",
     "submit": "enviar solicitação",
-    "close_cart": "fechar carrinho",
-    "cart_closed_success": "Carrinho fechado. Aguarde a aprovação dos profissionais.",
+    "close_cart": "fechar pedidos",
+    "cart_closed_success": "Pedidos fechados. Aguarde a aprovação dos profissionais.",
     "unknown": "Sem informação",
     "multiple_providers": "{count} profissionais",
     "loading": "Carregando...",
@@ -903,7 +906,7 @@
     "home": "Início",
     "search": "Busca",
     "agenda": "Agenda",
-    "cart": "Carrinho",
+    "cart": "Pedidos",
     "profile": "Perfil"
   },
   "provider": {
@@ -926,6 +929,11 @@
     "unread": "Não lidas",
     "mark_all_read": " Marcar todas como lidas"
   },
+  "genders": {
+    "female": "Feminino",
+    "male": "Masculino",
+    "not_informed": "Não informado"
+  },
   "support_request": {
     "title": "Falar com atendente",
     "subtitle": "Preencha os campos abaixo e um atendente humano entrará em contato com você.",

+ 54 - 33
src/layouts/MainLayout.vue

@@ -1,22 +1,26 @@
 <template>
-  <q-layout class="main-layout relative" view="hHh lpR fFf">
+  <q-layout
+    class="main-layout relative"
+    view="hHh lpR fFf"
+  >
     <q-header
       v-if="$q.screen.lt.sm"
       class="bg-surface"
-      :style="{
-        height: `env(safe-area-inset-top)`,
-      }"
+      :style="{ height: `env(safe-area-inset-top)` }"
     >
     </q-header>
 
     <q-page-container>
-      <q-page class="bg-surface main-layout-page" style="overflow: hidden;">
-        <q-scroll-area
-          ref="scrollAreaRef"
-          class="main-layout-scroll-area"
-          :content-active-style="{ width: '100%', maxWidth: '100%', overflowX: 'hidden', boxSizing: 'border-box' }"
-          :content-style="{ width: '100%', maxWidth: '100%', overflowX: 'hidden', boxSizing: 'border-box' }"
-          :style="scrollAreaHeight"
+      <q-page
+        class="bg-surface main-layout-page"
+        style="overflow: hidden"
+      >
+      <q-scroll-area
+        ref="scrollAreaRef"
+        class="main-layout-scroll-area"
+        :content-active-style="{ width: '100%', maxWidth: '100%', overflowX: 'hidden', boxSizing: 'border-box' }"
+        :content-style="{ width: '100%', maxWidth: '100%', overflowX: 'hidden', boxSizing: 'border-box' }"
+        :style="scrollAreaHeight"
         >
           <router-view v-slot="{ Component }">
             <Transition mode="out-in">
@@ -31,17 +35,24 @@
       </q-page>
     </q-page-container>
 
-    <q-footer v-if="$q.screen.lt.sm" class="provider-bottom-nav bg-white">
+    <q-footer
+      v-if="$q.screen.lt.sm"
+      class="provider-bottom-nav bg-white"
+    >
       <nav class="provider-bottom-nav-inner">
         <router-link
           v-for="item in navItems"
           :key="item.name"
-          :to="{ name: item.name }"
           :class="{ 'provider-bottom-nav-item--active': isNavItemActive(item) }"
+          :to="{ name: item.name }"
           class="provider-bottom-nav-item"
         >
           <span class="provider-bottom-nav-icon-wrap">
-            <q-icon :name="item.icon" class="provider-bottom-nav-icon" />
+            <q-icon
+              class="provider-bottom-nav-icon"
+              :name="item.icon"
+            />
+
             <q-badge
               v-if="item.name === 'ScheduleCartPage' && cartCount"
               color="secondary"
@@ -66,43 +77,53 @@ import { computed, useTemplateRef, watch } from "vue";
 import { useI18n } from "vue-i18n";
 import { useQuasar } from "quasar";
 import { useRoute } from "vue-router";
-import { useScheduleCartStore } from "src/stores/scheduleCart";
+import { useScheduleCartStore } from "src/stores/orders";
 
 defineOptions({
   name: "MainLayout",
 });
 
-const $q    = useQuasar();
+const { t } = useI18n();
 const route = useRoute();
-
 const scrollAreaRef = useTemplateRef("scrollAreaRef");
-
-const { t } = useI18n();
-
-let oldValue = route.path;
-
-const navItems = computed(() => [
-  { name: "DashboardPage",     label: t('nav.home'),    icon: "mdi-home-outline" },
-  { name: "SearchPage",        label: t('nav.search'),  icon: "mdi-magnify" },
-  { name: "CalendarPage",      label: t('nav.agenda'),  icon: "mdi-calendar-blank-outline" },
-  { name:  "ScheduleCartPage", label: t('nav.cart'),    icon: "mdi-cart-outline" },
-  { name:  "ProfilePage",      label: t('nav.profile'), icon: "mdi-account-circle-outline" },
-]);
+const $q = useQuasar();
 
 const cart = useScheduleCartStore();
 
 const cartCount = computed(() => cart.items.length);
 
-const isNavItemActive = (item) => route.name === item.name;
+const navItems = computed(() => [
+  { name: "DashboardPage", label: t("nav.home"), icon: "mdi-home-outline" },
+  { name: "SearchPage", label: t("nav.search"), icon: "mdi-magnify" },
+  {
+    name: "CalendarPage",
+    label: t("nav.agenda"),
+    icon: "mdi-calendar-blank-outline",
+  },
+  {
+    name: "ScheduleCartPage",
+    label: t("nav.cart"),
+    icon: "mdi-shopping-outline",
+  },
+  {
+    name: "ProfilePage",
+    label: t("nav.profile"),
+    icon: "mdi-account-circle-outline",
+  },
+]);
 
 const scrollAreaHeight = computed(() => {
   if ($q.screen.lt.sm) {
-    return 'height: calc(100dvh - env(safe-area-inset-top) - 60px - env(safe-area-inset-bottom)) !important;';
+    return "height: calc(100dvh - env(safe-area-inset-top) - 60px - env(safe-area-inset-bottom)) !important;";
   }
 
-  return 'height: 100dvh !important;';
+  return "height: 100dvh !important;";
 });
 
+let oldValue = route.path;
+
+const isNavItemActive = (item) => route.name === item.name;
+
 watch(
   () => route.path,
   (value) => {
@@ -112,7 +133,7 @@ watch(
     }
 
     oldValue = value;
-  }
+  },
 );
 </script>
 <style scoped>

+ 122 - 31
src/pages/LoginPage.vue

@@ -1,25 +1,50 @@
 <template>
   <q-page class="bg-surface-dark login-page">
-    <Transition mode="out-in" name="fade-slide">
+    <Transition
+      mode="out-in"
+      name="fade-slide"
+    >
       <div
         v-if="!clicked"
         key="splash"
         class="splash-screen"
         @click="clicked = true"
       >
-        <img class="splash-layer splash-layer--bg" :src="BackgroundLogin" />
-
-        <img class="splash-layer splash-layer--photo" :src="FotoDiarista" />
-
-        <img class="splash-layer splash-layer--logo" :src="LogoLogin" />
+        <img
+          class="splash-layer splash-layer--bg"
+          :src="BackgroundLogin"
+        />
+
+        <img
+          class="splash-layer splash-layer--photo"
+          :src="FotoDiarista"
+        />
+
+        <img
+          class="splash-layer splash-layer--logo"
+          :src="LogoLogin"
+        />
       </div>
 
-      <div v-else-if="steps === 5" key="step5" class="splash-screen">
-        <img class="splash-layer splash-layer--bg" :src="BackgroundLogin" />
+      <div
+        v-else-if="steps === 5"
+        key="step5"
+        class="splash-screen"
+      >
+        <img
+          class="splash-layer splash-layer--bg"
+          :src="BackgroundLogin"
+        />
 
-        <img class="splash-layer splash-layer--photo" :src="FotoDiarista" />
+        <img
+          class="splash-layer splash-layer--photo"
+          :src="FotoDiarista"
+        />
 
-        <img class="splash-layer splash-layer--logo-small" :src="LogoLogin" />
+        <img
+          class="splash-layer splash-layer--logo-small"
+          :src="LogoLogin"
+        />
 
         <div class="step4-card-wrapper">
           <LoginStep4Panel
@@ -30,7 +55,11 @@
         </div>
       </div>
 
-      <div v-else key="flow" class="flow-screen">
+      <div
+        v-else
+        key="flow"
+        class="flow-screen"
+      >
         <div class="flow-header items-center row">
           <q-btn
             v-if="steps >= 4"
@@ -45,10 +74,16 @@
         </div>
 
         <div class="flow-logo q-my-xl">
-          <q-img style="max-width: 180px" :src="LogoDiariaCampos" />
+          <q-img
+            :src="LogoDiariaCampos"
+            style="max-width: 180px"
+          />
         </div>
 
-        <div v-if="steps === 3" class="register-later-row">
+        <div
+          v-if="steps === 3"
+          class="register-later-row"
+        >
           <q-btn
             align="right"
             class="register-later-btn"
@@ -57,7 +92,7 @@
             icon-right="mdi-chevron-right-circle-outline"
             no-caps
             :label="$t('auth.register_later')"
-            @click="steps = 5"
+            @click="goToLocationStep"
           />
         </div>
 
@@ -72,17 +107,24 @@
         >
           <div
             class="flow-content"
-            :class="{ 'flow-content--centered': steps < 3 }"
+            :class="{ 'flow-content--centered': steps < 3, 'flow-content--email-only': steps === 1 && !smsEnabled }"
           >
             <LoginStep1Panel
               v-if="steps === 1"
               v-model:email="email"
               v-model:phone="phone"
+              :sms-enabled="smsEnabled"
             />
 
-            <LoginStep2Panel v-else-if="steps === 2" v-model:code="code" />
+            <LoginStep2Panel
+              v-else-if="steps === 2"
+              v-model:code="code"
+            />
 
-            <LoginStep3Panel v-else-if="steps === 3" v-model="stepThreeForm" />
+            <LoginStep3Panel
+              v-else-if="steps === 3"
+              v-model="stepThreeForm"
+            />
 
             <LoginStepAvatarPanel
               v-else-if="steps === 4"
@@ -112,36 +154,39 @@
 </template>
 
 <script setup>
+import BackgroundLogin from "src/assets/background-login.svg";
+import FotoDiarista from "src/assets/foto_diarista_login.svg";
+import LogoDiariaCampos from "src/assets/logo_diaria_campos_login.svg";
+import LogoLogin from "src/assets/logo_diaria_login.svg";
+
 import {
   createUserAndClient,
   sendCode,
   updateMe,
   validateCode,
 } from "src/api/user";
-import { useAuth } from "src/composables/useAuth";
-import { useRegistrationFlowStore } from "src/stores/registrationFlow";
-import { userStore } from "src/stores/user";
-import { useQuasar } from "quasar";
+
+import { getServicesConfig } from "src/api/services";
 import { onMounted, ref } from "vue";
+import { useAuth } from "src/composables/useAuth";
 import { useI18n } from "vue-i18n";
+import { useQuasar } from "quasar";
+import { useRegistrationFlowStore } from "src/stores/registrationFlow";
 import { useRouter } from "vue-router";
+import { userStore } from "src/stores/user";
 
-import BackgroundLogin from "src/assets/background-login.svg";
-import FotoDiarista from "src/assets/foto_diarista_login.svg";
-import LogoDiariaCampos from "src/assets/logo_diaria_campos_login.svg";
-import LogoLogin from "src/assets/logo_diaria_login.svg";
-
-import LoginStepAvatarPanel from "src/components/login/LoginStepAvatarPanel.vue";
 import LoginStep1Panel from "src/components/login/LoginStep1Panel.vue";
 import LoginStep2Panel from "src/components/login/LoginStep2Panel.vue";
 import LoginStep3Panel from "src/components/login/LoginStep3Panel.vue";
 import LoginStep4Panel from "src/components/login/LoginStep4Panel.vue";
+import LoginStepAvatarPanel from "src/components/login/LoginStepAvatarPanel.vue";
 
 const { setAuthDataFromPayload } = useAuth();
-const flowStore = useRegistrationFlowStore();
-const $q = useQuasar();
 const { t } = useI18n();
 const router = useRouter();
+const $q = useQuasar();
+
+const flowStore = useRegistrationFlowStore();
 
 const clicked = ref(false);
 const code = ref("");
@@ -152,6 +197,8 @@ const loginForm = ref(null);
 
 const phone = ref("");
 
+const smsEnabled = ref(false);
+
 const stepAvatarForm = ref({
   avatar: null,
 });
@@ -172,6 +219,7 @@ const stepThreeForm = ref({
   nickname: "",
   no_complement: false,
   number: "",
+  phone: "",
   state: "",
   zip_code: "",
 });
@@ -186,8 +234,26 @@ const goBack = () => {
   }
 };
 
+const goToLocationStep = () => {
+  saveRegistrationData();
+
+  steps.value = 5;
+};
+
+const loadServicesConfig = async () => {
+  try {
+    const config = await getServicesConfig();
+
+    smsEnabled.value = config?.sms_enabled === true;
+  } catch (error) {
+    console.error("Failed to load services config", error);
+
+    smsEnabled.value = false;
+  }
+};
+
 const navigateToMap = (lat, lng) => {
-  flowStore.setCredentials(email.value, phone.value, code.value);
+  saveRegistrationData();
   flowStore.setInitialLocation(lat, lng);
   router.push({ name: "LocationMapPage" });
 };
@@ -258,7 +324,7 @@ const registerUserAndClient = async () => {
   append("code", code.value);
   append("email", email.value);
   append("has_complement", !stepThreeForm.value.no_complement);
-  append("phone", phone.value);
+  append("phone", stepThreeForm.value.phone || phone.value);
 
   try {
     const response = await createUserAndClient(payload);
@@ -286,6 +352,8 @@ const registerUserAndClient = async () => {
   }
 };
 
+//
+
 const fileToBase64 = (file) =>
   new Promise((resolve, reject) => {
     const reader = new FileReader();
@@ -294,6 +362,18 @@ const fileToBase64 = (file) =>
     reader.readAsDataURL(file);
   });
 
+const saveRegistrationData = () => {
+  const registrationPhone = stepThreeForm.value.phone || phone.value;
+
+  flowStore.setCredentials(email.value, registrationPhone, code.value);
+
+  flowStore.setRegistrationData({
+    document: stepThreeForm.value.document,
+    name: stepThreeForm.value.name,
+    phone: registrationPhone,
+  });
+};
+
 const sendValidationCode = async () => {
   const response = await sendCode(email.value, phone.value);
 
@@ -319,6 +399,7 @@ const validateCodeInput = async () => {
       await setAuthDataFromPayload(response.data.payload);
       router.push({ name: "DashboardPage" });
     } else {
+      stepThreeForm.value.phone = phone.value;
       steps.value = 3;
     }
   } else {
@@ -326,7 +407,11 @@ const validateCodeInput = async () => {
   }
 };
 
+//
+
 onMounted(() => {
+  loadServicesConfig();
+
   setTimeout(() => {
     clicked.value = true;
   }, 1500);
@@ -455,6 +540,12 @@ onMounted(() => {
     align-items: center;
     justify-content: center;
   }
+
+  &--email-only {
+    align-items: flex-start;
+    justify-content: flex-start;
+    padding-top: clamp(96px, 16vh, 140px);
+  }
 }
 
 .flow-footer {

+ 225 - 114
src/pages/agenda/CalendarPage.vue

@@ -1,51 +1,97 @@
 <template>
   <q-page class="bg-page q-pb-xl">
-    <div class="calendar-header row items-center q-px-md q-pt-md q-pb-sm bg-white">
-      <q-btn flat round dense icon="mdi-chevron-left" color="primary" @click="router.back()" />
-
-      <div class="col text-center font16 fontbold gradient-diarista text-primary">
-        {{ $t('dashboard_client.agenda.title') }}
+    <div
+      class="calendar-header row items-center q-px-md q-pt-md q-pb-sm bg-white"
+    >
+      <q-btn
+        color="primary"
+        dense
+        flat
+        icon="mdi-chevron-left"
+        round
+        @click="router.back()"
+      />
+
+      <div
+        class="col text-center font16 fontbold gradient-diarista text-primary"
+      >
+        {{ $t("dashboard_client.agenda.title") }}
       </div>
 
       <div style="width: 36px" />
     </div>
 
     <template v-if="loading">
-      <div class="row items-center justify-center full-width" style="height: 60vh">
+      <div
+        class="row items-center justify-center full-width"
+        style="height: 60vh"
+      >
         <q-spinner-dots color="primary" />
       </div>
     </template>
 
     <template v-else>
       <div class="q-mt-md q-mx-md">
-        <div class="section-title gradient-diarista q-mb-sm font16 fontbold">{{ $t('dashboard_client.agenda.upcoming_title') }}</div>
+        <div class="section-title gradient-diarista q-mb-sm font16 fontbold">
+          {{ $t("dashboard_client.agenda.upcoming_title") }}
+        </div>
 
         <template v-if="upcomingSchedules.length > 0">
           <q-card
             v-for="item in upcomingSchedules"
             :key="item.id"
-            class="calendar-card bg-surface shadow-card q-mb-sm"
             :flat="false"
+            class="calendar-card bg-surface shadow-card q-mb-sm"
           >
             <q-card-section class="q-pa-sm">
               <div class="row no-wrap items-start q-gutter-x-sm">
                 <q-avatar size="44px">
-                  <img v-if="item.provider_photo" :src="item.provider_photo" style="object-fit:cover" />
-                  <span v-else class="full-width full-height flex flex-center" :style="avatarColors[item.id % avatarColors.length]" style="border-radius:50%">{{ getFirstName(item.provider_name)?.slice(0, 2).toUpperCase() ?? '??' }}</span>
+                  <img
+                    v-if="item.provider_photo"
+                    :src="item.provider_photo"
+                    style="object-fit: cover"
+                  />
+
+                  <span
+                    v-else
+                    class="full-width full-height flex flex-center"
+                    style="border-radius: 50%"
+                    :style="avatarColors[item.id % avatarColors.length]"
+                  >
+                    {{
+                      getFirstName(item.provider_name)
+                        ?.slice(0, 2)
+                        .toUpperCase() ?? "??"
+                    }}
+                  </span>
                 </q-avatar>
+
                 <div class="col column text-text">
-                  <span class="font12 fontbold">{{ getFirstName(item.provider_name) || $t('dashboard_client.agenda.waiting_proposals') }}</span>
+                  <span class="font12 fontbold">
+                    {{
+                      getFirstName(item.provider_name) ||
+                      $t("dashboard_client.agenda.waiting_proposals")
+                    }}
+                  </span>
+
                   <div class="row items-center no-wrap">
-                    <span class="font10 fontbold">{{ formatWeekday(item.date) }}</span>
-                    <span class="font10 fontmedium">{{ ', ' + formatDayMonth(item.date) }}</span>
+                    <span class="font10 fontbold">
+                      {{ formatWeekday(item.date) }}
+                    </span>
+
+                    <span class="font10 fontmedium">
+                      {{ ", " + formatDayMonth(item.date) }}
+                    </span>
                   </div>
+
                   <span class="">
                     <span class="font10 fontmedium">
-                      {{ $t('dashboard_client.next_schedules.from') }}                  
+                      {{ $t("dashboard_client.next_schedules.from") }}
                     </span>
+
                     <span class="font10 fontbold q-ml-xs">
                       {{ item.start_time?.slice(0, 5) }}
-                      {{ $t('dashboard_client.next_schedules.to') }}
+                      {{ $t("dashboard_client.next_schedules.to") }}
                       {{ item.end_time?.slice(0, 5) }}
                     </span>
                   </span>
@@ -53,30 +99,42 @@
 
                 <div class="col-auto column items-end">
                   <q-chip
+                    class="font10"
                     dense
                     square
                     :color="statusBgColor(item.status)"
-                    :text-color="statusTextColor(item.status)"
                     :label="statusLabel(item.status)"
-                    class="font10"
+                    :text-color="statusTextColor(item.status)"
                   />
-                  <span class="text-price font12 fontbold">{{ formatScheduleTotal(item) }}</span>
-                  <span class="text-period font10 fontmedium">{{ periodLabel(item.period_type) }}</span>
+
+                  <span class="text-price font12 fontbold">
+                    {{ formatScheduleTotal(item) }}
+                  </span>
+
+                  <span class="text-period font10 fontmedium">
+                    {{ periodLabel(item.period_type) }}
+                  </span>
                 </div>
               </div>
 
               <div class="row items-center no-wrap q-mt-xs">
                 <span class="type-label text-primary font10 fontmedium">
-                  {{ item.schedule_type === 'custom' ? $t('dashboard_client.agenda.type_custom') : $t('dashboard_client.agenda.type_default') }}
+                  {{
+                    item.schedule_type === "custom"
+                      ? $t("dashboard_client.agenda.type_custom")
+                      : $t("dashboard_client.agenda.type_default")
+                  }}
                 </span>
+
                 <q-space />
+
                 <q-btn
-                  rounded
-                  no-caps
+                  class=""
                   color="primary"
-                  size="sm"
+                  no-caps
                   padding="4px 8px"
-                  class=""
+                  rounded
+                  size="sm"
                   :label="$t('dashboard_client.agenda.btn_view_details')"
                   @click="openDetailsDialog(item)"
                 />
@@ -85,41 +143,75 @@
           </q-card>
         </template>
 
-        <div v-else class="text-center text-grey-5 q-py-lg text-body2">
-          {{ $t('dashboard_client.agenda.empty_upcoming') }}
+        <div
+          v-else
+          class="text-center text-grey-5 q-py-lg text-body2"
+        >
+          {{ $t("dashboard_client.agenda.empty_upcoming") }}
         </div>
       </div>
 
       <div class="q-mt-lg q-mx-md">
-        <div class="section-title gradient-diarista q-mb-sm font16 fontbold">{{ $t('dashboard_client.agenda.completed_title') }}</div>
+        <div class="section-title gradient-diarista q-mb-sm font16 fontbold">
+          {{ $t("dashboard_client.agenda.completed_title") }}
+        </div>
 
         <template v-if="completedSchedules.length > 0">
           <q-card
             v-for="item in completedSchedules"
             :key="item.id"
-            class="calendar-card bg-surface shadow-card q-mb-sm"
             :flat="false"
+            class="calendar-card bg-surface shadow-card q-mb-sm"
           >
             <q-card-section class="q-pa-sm">
               <div class="row no-wrap items-start q-gutter-x-sm">
                 <q-avatar size="44px">
-                  <img v-if="item.provider_photo" :src="item.provider_photo" style="object-fit:cover" />
-                  <span v-else class="full-width full-height flex flex-center" :style="avatarColors[item.id % avatarColors.length]" style="border-radius:50%">{{ getFirstName(item.provider_name)?.slice(0, 2).toUpperCase() ?? '??' }}</span>
+                  <img
+                    v-if="item.provider_photo"
+                    :src="item.provider_photo"
+                    style="object-fit: cover"
+                  />
+
+                  <span
+                    v-else
+                    class="full-width full-height flex flex-center"
+                    style="border-radius: 50%"
+                    :style="avatarColors[item.id % avatarColors.length]"
+                  >
+                    {{
+                      getFirstName(item.provider_name)
+                        ?.slice(0, 2)
+                        .toUpperCase() ?? "??"
+                    }}
+                  </span>
                 </q-avatar>
 
                 <div class="col column text-text">
-                  <span class="font12 fontbold">{{ getFirstName(item.provider_name) || $t('dashboard_client.agenda.waiting_proposals') }}</span>
+                  <span class="font12 fontbold">
+                    {{
+                      getFirstName(item.provider_name) ||
+                      $t("dashboard_client.agenda.waiting_proposals")
+                    }}
+                  </span>
+
                   <div class="row items-center no-wrap">
-                    <span class="font10 fontbold">{{ formatWeekday(item.date) }}</span>
-                    <span class="font10 fontmedium">{{ ', ' + formatDayMonth(item.date) }}</span>
+                    <span class="font10 fontbold">
+                      {{ formatWeekday(item.date) }}
+                    </span>
+
+                    <span class="font10 fontmedium">
+                      {{ ", " + formatDayMonth(item.date) }}
+                    </span>
                   </div>
+
                   <span class="">
                     <span class="font10 fontmedium">
-                      {{ $t('dashboard_client.next_schedules.from') }}                  
+                      {{ $t("dashboard_client.next_schedules.from") }}
                     </span>
+
                     <span class="font10 fontbold q-ml-xs">
                       {{ item.start_time?.slice(0, 5) }}
-                      {{ $t('dashboard_client.next_schedules.to') }}
+                      {{ $t("dashboard_client.next_schedules.to") }}
                       {{ item.end_time?.slice(0, 5) }}
                     </span>
                   </span>
@@ -127,54 +219,68 @@
 
                 <div class="col-auto column items-end">
                   <q-chip
+                    class="font10"
                     dense
                     square
                     :color="statusBgColor(item.status)"
-                    :text-color="statusTextColor(item.status)"
                     :label="statusLabel(item.status)"
-                    class="font10"
+                    :text-color="statusTextColor(item.status)"
                   />
-                  <span class="text-price font12 fontbold">{{ formatScheduleTotal(item) }}</span>
-                  <span class="text-period font10 fontmedium">{{ periodLabel(item.period_type) }}</span>
+
+                  <span class="text-price font12 fontbold">
+                    {{ formatScheduleTotal(item) }}
+                  </span>
+
+                  <span class="text-period font10 fontmedium">
+                    {{ periodLabel(item.period_type) }}
+                  </span>
                 </div>
               </div>
 
               <div class="row items-center no-wrap q-mt-xs">
                 <span class="type-label text-primary font10 fontmedium">
-                  {{ item.schedule_type === 'custom' ? $t('dashboard_client.agenda.type_custom') : $t('dashboard_client.agenda.type_default') }}
+                  {{
+                    item.schedule_type === "custom"
+                      ? $t("dashboard_client.agenda.type_custom")
+                      : $t("dashboard_client.agenda.type_default")
+                  }}
                 </span>
+
                 <q-space />
+
                 <q-rating
-                  :model-value="item.client_reviewed ? item.client_stars : 0"
-                  :max="5"
-                  size="14px"
+                  class="q-mr-sm"
                   color="amber"
                   icon="mdi-star-outline"
                   icon-selected="mdi-star"
                   readonly
-                  class="q-mr-sm"
+                  size="14px"
+                  :max="5"
+                  :model-value="item.client_reviewed ? item.client_stars : 0"
                 />
+
                 <q-btn
                   v-if="item.client_reviewed"
-                  unelevated
-                  rounded
-                  no-caps
+                  class="btn-rate"
                   color="secondary"
-                  size="sm"
+                  no-caps
                   padding="4px 8px"
-                  class="btn-rate"
+                  rounded
+                  size="sm"
+                  unelevated
                   :label="$t('dashboard_client.agenda.btn_reschedule')"
                   @click="openSchedulingDialog(item)"
                 />
+
                 <q-btn
                   v-else
-                  unelevated
-                  rounded
-                  no-caps
+                  class="btn-rate"
                   color="secondary"
-                  size="sm"
+                  no-caps
                   padding="4px 8px"
-                  class="btn-rate"
+                  rounded
+                  size="sm"
+                  unelevated
                   :label="$t('dashboard_client.agenda.btn_rate')"
                   @click="openRatingDialog(item)"
                 />
@@ -183,8 +289,11 @@
           </q-card>
         </template>
 
-        <div v-else class="text-center text-grey-5 q-py-lg text-body2">
-          {{ $t('dashboard_client.agenda.empty_completed') }}
+        <div
+          v-else
+          class="text-center text-grey-5 q-py-lg text-body2"
+        >
+          {{ $t("dashboard_client.agenda.empty_completed") }}
         </div>
       </div>
     </template>
@@ -192,102 +301,112 @@
 </template>
 
 <script setup>
-import { ref, onMounted } from 'vue';
-import { useQuasar } from 'quasar';
-import { useI18n } from 'vue-i18n';
-import { getClientCalendar } from 'src/api/clientCalendar';
-import { formatCurrency,getFirstName } from 'src/helpers/utils';
-import { getSchedulePaymentType, getScheduleTotalWithPlatformFee } from 'src/helpers/paymentPlatformFees';
-import { usePaymentPlatformFees } from 'src/composables/usePaymentPlatformFees';
-import NextSchedulesDetailsDialog from 'src/components/dashboard/NextSchedulesDetailsDialog.vue';
-import ScheduleRatingDialog from 'src/components/dashboard/ScheduleRatingDialog.vue';
-import SchedulingDialog from 'src/pages/search/components/SchedulingDialog.vue';
-import { useRouter } from 'vue-router';
+import { avatarColors } from "src/helpers/avatarColors";
+import { formatCurrency, getFirstName } from "src/helpers/utils";
+import { getClientCalendar } from "src/api/clientCalendar";
+
+import {
+  getSchedulePaymentType,
+  getScheduleTotalWithPlatformFee,
+} from "src/helpers/paymentPlatformFees";
+
+import { onMounted, ref } from "vue";
+import { useI18n } from "vue-i18n";
+import { usePaymentPlatformFees } from "src/composables/usePaymentPlatformFees";
+import { useQuasar } from "quasar";
+import { useRouter } from "vue-router";
+
+import NextSchedulesDetailsDialog from "src/components/dashboard/NextSchedulesDetailsDialog.vue";
+import ScheduleRatingDialog from "src/components/dashboard/ScheduleRatingDialog.vue";
+import SchedulingDialog from "src/pages/search/components/SchedulingDialog.vue";
 
 const $q = useQuasar();
 const { t } = useI18n();
 const router = useRouter();
 const { platformFees, loadPlatformFees } = usePaymentPlatformFees();
 
-const avatarColors = [
-  { background: '#ffd5df', color: '#932e57' },
-  { background: '#d7e8ff', color: '#2158a8' },
-  { background: '#dfd',    color: '#2a7a3b' },
-  { background: '#ffe5cc', color: '#8a4500' },
-];
 const loading = ref(true);
 const upcomingSchedules = ref([]);
 const completedSchedules = ref([]);
 
 const parseLocalDate = (dateStr) => {
   if (!dateStr) return null;
-  const s = String(dateStr);
-  const iso = s.match(/^(\d{4})-(\d{2})-(\d{2})/);
+
+  const iso = dateStr.match(/^(\d{4})-(\d{2})-(\d{2})/);
   if (iso) return new Date(+iso[1], +iso[2] - 1, +iso[3]);
-  const dmy = s.match(/^(\d{2})\/(\d{2})\/(\d{4})/);
+  const dmy = dateStr.match(/^(\d{2})\/(\d{2})\/(\d{4})/);
   if (dmy) return new Date(+dmy[3], +dmy[2] - 1, +dmy[1]);
   return null;
 };
 
 const formatWeekday = (dateStr) => {
   const d = parseLocalDate(dateStr);
-  if (!d) return '';
-  const w = d.toLocaleDateString('pt-BR', { weekday: 'long' });
+  if (!d) return "";
+  const w = d.toLocaleDateString("pt-BR", { weekday: "long" });
   return w.charAt(0).toUpperCase() + w.slice(1);
 };
 
 const formatDayMonth = (dateStr) => {
   const d = parseLocalDate(dateStr);
-  if (!d) return '';
-  return d.toLocaleDateString('pt-BR', { day: '2-digit', month: '2-digit' });
+  if (!d) return "";
+  return d.toLocaleDateString("pt-BR", { day: "2-digit", month: "2-digit" });
 };
 
 const periodLabel = (periodType) => {
   const key = `period_types.${periodType}`;
   const translated = t(key);
-  return translated !== key ? translated : '';
+  return translated !== key ? translated : "";
 };
 
-const formatScheduleTotal = (item) => (
-  formatCurrency(getScheduleTotalWithPlatformFee(item, getSchedulePaymentType(item), platformFees.value))
-);
+//
+
+const formatScheduleTotal = (item) =>
+  formatCurrency(
+    getScheduleTotalWithPlatformFee(
+      item,
+      getSchedulePaymentType(item),
+      platformFees.value,
+    ),
+  );
 
 const statusLabel = (status) => {
   const map = {
-    pending: t('dashboard_client.agenda.status_pending'),
-    accepted: t('dashboard_client.agenda.status_accepted'),
-    paid: t('dashboard_client.agenda.status_paid'),
-    started: t('dashboard_client.agenda.status_started'),
-    finished: t('dashboard_client.agenda.status_finished'),
-    cancelled: t('dashboard_client.agenda.status_cancelled'),
+    pending: t("dashboard_client.agenda.status_pending"),
+    accepted: t("dashboard_client.agenda.status_accepted"),
+    paid: t("dashboard_client.agenda.status_paid"),
+    started: t("dashboard_client.agenda.status_started"),
+    finished: t("dashboard_client.agenda.status_finished"),
+    cancelled: t("dashboard_client.agenda.status_cancelled"),
   };
   return map[status] ?? status;
 };
 
 const statusBgColor = (status) => {
   const map = {
-    pending: 'warning-bg',
-    accepted: 'success-bg',
-    paid: 'success-bg',
-    started: 'info-bg',
-    finished: 'neutral-bg',
-    cancelled: 'secondary-bg',
+    pending: "warning-bg",
+    accepted: "success-bg",
+    paid: "success-bg",
+    started: "info-bg",
+    finished: "neutral-bg",
+    cancelled: "secondary-bg",
   };
-  return map[status] ?? 'neutral-bg';
+  return map[status] ?? "neutral-bg";
 };
 
 const statusTextColor = (status) => {
   const map = {
-    pending: 'warning',
-    accepted: 'success',
-    paid: 'success',
-    started: 'info',
-    finished: 'status-finished',
-    cancelled: 'secondary',
+    pending: "warning",
+    accepted: "success",
+    paid: "success",
+    started: "info",
+    finished: "status-finished",
+    cancelled: "secondary",
   };
-  return map[status] ?? 'text';
+  return map[status] ?? "text";
 };
 
+//
+
 const loadCalendar = async () => {
   const response = await getClientCalendar();
   if (response) {
@@ -301,7 +420,7 @@ const openDetailsDialog = (schedule) => {
     component: NextSchedulesDetailsDialog,
     componentProps: { schedule },
   }).onOk(async ({ action }) => {
-    if (action === 'cancelled') {
+    if (action === "cancelled") {
       await loadCalendar();
     }
   });
@@ -332,10 +451,7 @@ const openSchedulingDialog = (item) => {
 };
 
 onMounted(async () => {
-  await Promise.all([
-    loadCalendar(),
-    loadPlatformFees().catch(() => {}),
-  ]);
+  await Promise.all([loadCalendar(), loadPlatformFees().catch(() => {})]);
   loading.value = false;
 });
 </script>
@@ -354,9 +470,6 @@ onMounted(async () => {
   line-height: 1.2;
 }
 
-
-
-
 .text-price {
   color: #3a3a4a;
   white-space: nowrap;
@@ -368,9 +481,7 @@ onMounted(async () => {
   white-space: nowrap;
 }
 
-
 .btn-rate {
   padding: 3px 10px;
 }
-
 </style>

+ 126 - 63
src/pages/dashboard/components/DashboardClientProposals.vue

@@ -1,42 +1,82 @@
 <template>
-  <div class="scroll-wrapper">    
-    <div class="dashboard-section-title gradient-diarista q-mb-sm font16 fontbold q-px-md">{{ $t('dashboard_client.client_proposals.title') }}</div>
+  <div class="scroll-wrapper">
+    <div
+      class="dashboard-section-title gradient-diarista q-mb-sm font16 fontbold q-px-md"
+    >
+      {{ $t("dashboard_client.client_proposals.title") }}
+    </div>
 
     <div class="scroll-track q-pb-md q-px-md text-text">
-
-      <q-card v-for="item in data" :key="item.id" class="proposal-card shadow-card" :flat="false">
+      <q-card
+        v-for="item in data"
+        :key="item.id"
+        :flat="false"
+        class="proposal-card shadow-card"
+      >
         <div class="row no-wrap">
           <div class="column">
-            <q-avatar :style="avatarColors[item.id % avatarColors.length]" class="q-mx-auto">
-              <img v-if="item.provider_photo" :src="item.provider_photo" style="object-fit:cover;border-radius:50%;" />
-              <span v-else>{{ item.provider_name?.slice(0, 1) ?? '—' }}</span>
+            <q-avatar
+              class="q-mx-auto"
+              :style="avatarColors[item.id % avatarColors.length]"
+            >
+              <img
+                v-if="item.provider_photo"
+                :src="item.provider_photo"
+                style="object-fit: cover; border-radius: 50%"
+              />
+
+              <span v-else>
+                {{ item.provider_name?.slice(0, 1) ?? "—" }}
+              </span>
             </q-avatar>
-            <q-space/>
+
+            <q-space />
+
             <div class="customColor font8 fontbold">
               <div>
-                {{ $t('dashboard_client.client_proposals.candidate') }} 
+                {{ $t("dashboard_client.client_proposals.candidate") }}
               </div>
+
               <div class="no-wrap">
-                {{ $t('dashboard_client.client_proposals.custom') }}
+                {{ $t("dashboard_client.client_proposals.custom") }}
               </div>
             </div>
           </div>
 
           <div class="content">
-
             <div class="row justify-between items-center text-text">
               <div>
                 <div class="font12 fontmedium">
                   {{ item.provider_name }}
-                  <span class="font9 fontregular">{{ $t('dashboard_client.client_proposals.age', { idade: item.idade }) }}</span>
+
+                  <span class="font9 fontregular">
+                    {{
+                      $t("dashboard_client.client_proposals.age", {
+                        idade: item.idade,
+                      })
+                    }}
+                  </span>
                 </div>
+
+                <span
+                  v-if="getProviderGenderLabel(item, t)"
+                  class="font9 fontregular text-grey-7"
+                >
+                  {{ getProviderGenderLabel(item, t) }}
+                </span>
+
                 <span class="font10 fontmedium">
-                  <q-icon name="star" size="12px" color="warning" class="font9 fontregular"/>
+                  <q-icon
+                    class="font9 fontregular"
+                    color="warning"
+                    name="star"
+                    size="12px"
+                  />
+
                   <span>
                     {{ item.average_rating }}
                   </span>
                 </span>
-
               </div>
             </div>
 
@@ -47,17 +87,19 @@
                 </span>
 
                 <span class="text-schedule-date-regular">
-                  {{ ', ' + formatDayMonth(item.date) }}
+                  {{ ", " + formatDayMonth(item.date) }}
                 </span>
               </div>
 
               <div class="schedule-line q-pt-xs">
                 <span class="text-schedule-date-regular">
-                  {{ $t('dashboard_client.next_schedules.from') }}&nbsp;
+                  {{ $t("dashboard_client.next_schedules.from") }}&nbsp;
                 </span>
 
                 <span class="text-schedule-date-bold fontbold">
-                  {{ item.start_time?.slice(0,5) }} {{ $t('dashboard_client.next_schedules.to') }} {{ item.end_time?.slice(0,5) }}
+                  {{ item.start_time?.slice(0, 5) }}
+                  {{ $t("dashboard_client.next_schedules.to") }}
+                  {{ item.end_time?.slice(0, 5) }}
                 </span>
               </div>
             </div>
@@ -66,61 +108,79 @@
             <div class="price text-text">
               <span class="font9 fontmedium distance">
                 <span class="fontbold font10">
-                  {{ $t('dashboard_client.client_proposals.distance') }}
+                  {{ $t("dashboard_client.client_proposals.distance") }}
                 </span>
+
                 <span class="font10 fontregular q-ml-sm">
-                  {{ (item.distance_km ?? '--') + ' km' }}
+                  {{ (item.distance_km ?? "--") + " km" }}
                 </span>
               </span>
+
               <span class="font14 fontbold">
-                {{ filterCurrency(chooseprice(item.period_type, item.daily_price_8h)) }}            
+                {{
+                  filterCurrency(
+                    chooseprice(item.period_type, item.daily_price_8h),
+                  )
+                }}
               </span>
 
               <span class="col-6 font10 fontmedium">
                 {{ formatLabelByPeriodType(item.period_type) }}
               </span>
-
             </div>
 
             <div class="actions">
-              <q-btn label="recusar" size="sm" padding="4px 12px" flat class="btn-reject" @click="() => handleRefuseProposal(item.id)" />
-              <q-btn label="aceitar" size="sm" padding="4px 12px" class="btn-accept" @click="() => handleAcceptProposal(item.id)" />
+              <q-btn
+                class="btn-reject"
+                flat
+                label="recusar"
+                padding="4px 12px"
+                size="sm"
+                @click="() => handleRefuseProposal(item.id)"
+              />
+
+              <q-btn
+                class="btn-accept"
+                label="aceitar"
+                padding="4px 12px"
+                size="sm"
+                @click="() => handleAcceptProposal(item.id)"
+              />
             </div>
-
           </div>
-
         </div>
       </q-card>
-
     </div>
   </div>
 </template>
 
 <script setup>
-import { acceptProposal, refuseProposal } from 'src/api/customSchedules'
-import { chooseprice, formatLabelByPeriodType, filterCurrency } from 'src/helpers/utils'
+import { acceptProposal, refuseProposal } from "src/api/customSchedules";
+import { avatarColors } from "src/helpers/avatarColors";
+
+import {
+  chooseprice,
+  filterCurrency,
+  formatLabelByPeriodType,
+} from "src/helpers/utils";
+
+import { getProviderGenderLabel } from "src/helpers/provider";
+import { useI18n } from "vue-i18n";
+
+const emit = defineEmits(["refreshData"]);
 
 defineProps({
   data: {
     type: Array,
-    default: () => []
-  }
-})
-
-
-const emit = defineEmits(['refreshData'])
+    default: () => [],
+  },
+});
 
-const avatarColors = [
-  { background: '#ffd5df', color: '#932e57' },
-  { background: '#d7e8ff', color: '#2158a8' },
-  { background: '#dfd', color: '#2a7a3b' },
-  { background: '#ffe5cc', color: '#8a4500' },
-];
+const { t } = useI18n();
 
 // const formatTime = (time) => {
 //   if (!time) return '';
 
-
 //   const [hour, minute] = time.split(':');
 //   return `${hour}:${minute}`;
 // };
@@ -140,32 +200,21 @@ const avatarColors = [
 //   return `${weekday}, ${day}-${month}`;
 // };
 
-const handleRefuseProposal = async (proposalId) => {
-  // isLoading.value = true
-  try {
-     await refuseProposal(proposalId)
-    
-    // await loadProposals()
-     emit('refreshData')
-  } catch (error) {
-    console.log(error);
-  } finally {
-    // isLoading.value = false
-  }
-}
-
 const formatDayMonth = (iso) => {
-  if (!iso) return '';
+  if (!iso) return "";
 
-  return new Date(iso).toLocaleDateString('pt-BR', { day: '2-digit', month: '2-digit' });
+  return new Date(iso).toLocaleDateString("pt-BR", {
+    day: "2-digit",
+    month: "2-digit",
+  });
 };
 
 const formatWeekday = (iso) => {
-  if (!iso) return '';
+  if (!iso) return "";
 
   const d = new Date(iso);
 
-  const w = d.toLocaleDateString('pt-BR', { weekday: 'long' });
+  const w = d.toLocaleDateString("pt-BR", { weekday: "long" });
 
   return w.charAt(0).toUpperCase() + w.slice(1);
 };
@@ -173,16 +222,30 @@ const formatWeekday = (iso) => {
 const handleAcceptProposal = async (proposalId) => {
   // isLoading.value = true
   try {
-    await acceptProposal(proposalId)
+    await acceptProposal(proposalId);
 
-     emit('refreshData')
+    emit("refreshData");
     // onDialogOK()
   } catch (error) {
     console.log(error);
   } finally {
     // isLoading.value = false
   }
-}
+};
+
+const handleRefuseProposal = async (proposalId) => {
+  // isLoading.value = true
+  try {
+    await refuseProposal(proposalId);
+
+    // await loadProposals()
+    emit("refreshData");
+  } catch (error) {
+    console.log(error);
+  } finally {
+    // isLoading.value = false
+  }
+};
 </script>
 
 <style scoped lang="scss">
@@ -271,7 +334,7 @@ const handleAcceptProposal = async (proposalId) => {
 }
 
 .customColor {
-  background: linear-gradient(180deg, #8B5CF6 50%, #EC4899 50%);
+  background: linear-gradient(180deg, #8b5cf6 50%, #ec4899 50%);
   background-clip: text;
   -webkit-background-clip: text;
   -webkit-text-fill-color: transparent;

+ 172 - 68
src/pages/location/AddressCompletionPage.vue

@@ -2,53 +2,107 @@
   <q-page class="address-completion-page bg-surface">
     <div class="address-completion-inner">
       <q-btn
-        flat
-        dense
-        round
+        class="address-completion-back"
         color="primary"
+        dense
+        flat
         icon="mdi-arrow-left"
-        class="address-completion-back"
+        round
         @click="router.back()"
       />
 
-      <div class="address-completion-content">
+      <q-form
+        ref="addressForm"
+        class="address-completion-content"
+      >
         <div class="text-text">
-          <span class="font14 fontbold">{{ $t('common.terms.address') }}</span>
+          <span class="font14 fontbold">{{ $t("common.terms.document") }}</span>
         </div>
+
         <q-input
-          :model-value="flowStore.confirmedAddress"
+          v-model="form.document"
+          class="bg-surface q-mt-sm q-mb-md"
+          hide-bottom-space
+          input-class="text-text"
+          lazy-rules
+          no-error-icon
           outlined
           rounded
-          readonly
+          :placeholder="$t('common.terms.document')"
+          :rules="[inputRules.cpfOrCnpj]"
+        />
+
+        <div class="text-text">
+          <span class="font14 fontbold">{{ $t("common.terms.phone") }}</span>
+        </div>
+
+        <q-input
+          v-model="form.phone"
+          class="bg-surface q-mt-sm q-mb-md"
+          hide-bottom-space
+          input-class="text-text"
+          lazy-rules
+          mask="(##) #####-####"
+          no-error-icon
+          outlined
+          rounded
+          :placeholder="$t('common.terms.phone')"
+          :rules="[inputRules.required, inputRules.phone]"
+        />
+
+        <div class="text-text">
+          <span class="font14 fontbold">{{ $t("common.terms.address") }}</span>
+        </div>
+
+        <q-input
+          :model-value="flowStore.confirmedAddress"
           class="bg-surface q-mt-sm q-mb-md"
           input-class="text-text"
+          outlined
+          readonly
+          rounded
         />
 
         <div class="row q-col-gutter-sm">
           <div class="col-4">
             <div class="text-text">
-              <span class="font14 fontbold">{{ $t('common.terms.address_number') }}</span>
+              <span class="font14 fontbold">{{
+                $t("common.terms.address_number")
+              }}</span>
             </div>
+
             <q-input
               v-model="form.number"
-              outlined
-              rounded
               class="bg-surface q-mt-sm q-mb-md"
-              placeholder="0000"
+              hide-bottom-space
               input-class="text-text"
+              lazy-rules
+              no-error-icon
+              outlined
+              placeholder="0000"
+              rounded
+              :rules="[inputRules.required]"
             />
           </div>
+
           <div class="col-8">
             <div class="text-text">
-              <span class="font14 fontbold">{{ $t('common.terms.district') }}</span>
+              <span class="font14 fontbold">{{
+                $t("common.terms.district")
+              }}</span>
             </div>
+
             <q-input
               :model-value="flowStore.confirmedDistrict"
-              outlined
-              rounded
-              readonly
+              :rules="[inputRules.required]"
               class="bg-surface q-mt-sm q-mb-md"
+              hide-bottom-space
               input-class="text-text"
+              lazy-rules
+              no-error-icon
+              outlined
+              readonly
+              rounded
             />
           </div>
         </div>
@@ -56,105 +110,123 @@
         <div class="row q-col-gutter-sm">
           <div class="col-8">
             <div class="text-text">
-              <span class="font14 fontbold">{{ $t('common.terms.city') }}</span>
+              <span class="font14 fontbold">{{ $t("common.terms.city") }}</span>
             </div>
+
             <q-input
               :model-value="flowStore.confirmedCity"
-              outlined
-              rounded
-              readonly
+              :rules="[inputRules.required]"
               class="bg-surface q-mt-sm q-mb-md"
+              hide-bottom-space
               input-class="text-text"
+              lazy-rules
+              no-error-icon
+              outlined
+              readonly
+              rounded
             />
           </div>
+
           <div class="col-4">
             <div class="text-text">
-              <span class="font14 fontbold">{{ $t('common.terms.state') }}</span>
+              <span class="font14 fontbold">
+                {{ $t("common.terms.state") }}
+              </span>
             </div>
+
             <q-input
               :model-value="flowStore.confirmedState"
-              outlined
-              rounded
-              readonly
+              :rules="[inputRules.required]"
               class="bg-surface q-mt-sm q-mb-md"
+              hide-bottom-space
               input-class="text-text"
+              lazy-rules
+              no-error-icon
+              outlined
+              readonly
+              rounded
             />
           </div>
         </div>
 
         <q-checkbox
           v-model="form.no_complement"
-          :label="$t('auth.no_complement')"
+          class="q-mb-md text-text"
           color="primary"
           keep-color
-          class="q-mb-md text-text"
+          :label="$t('auth.no_complement')"
         />
 
         <template v-if="!form.no_complement">
           <div class="text-text">
-            <span class="font14 fontbold">{{ $t('auth.complement') }}</span>
+            <span class="font14 fontbold">{{ $t("auth.complement") }}</span>
           </div>
+
           <q-input
             v-model="form.complement"
+            class="bg-surface q-mt-sm q-mb-md"
+            input-class="text-text"
             outlined
             rounded
-            class="bg-surface q-mt-sm q-mb-md"
             :placeholder="$t('auth.complement_placeholder')"
-            input-class="text-text"
           />
         </template>
 
         <div class="text-text">
-          <span class="font14 fontbold">{{ $t('auth.address_nickname') }}</span>
+          <span class="font14 fontbold">{{ $t("auth.address_nickname") }}</span>
         </div>
+
         <q-input
           v-model="form.nickname"
+          class="bg-surface q-mt-sm q-mb-md"
+          input-class="text-text"
           outlined
           rounded
-          class="bg-surface q-mt-sm q-mb-md"
           :placeholder="$t('auth.address_nickname_placeholder')"
-          input-class="text-text"
         />
 
         <div class="text-text">
-          <span class="font14 fontbold">{{ $t('auth.address_instructions') }}</span>
+          <span class="font14 fontbold">
+            {{ $t("auth.address_instructions") }}
+          </span>
         </div>
+
         <q-input
           v-model="form.instructions"
+          autogrow
+          class="bg-surface q-mt-sm q-mb-md"
+          input-class="text-text"
           outlined
           rounded
-          class="bg-surface q-mt-sm q-mb-md"
-          type="textarea"
           rows="3"
-          autogrow
-          input-class="text-text"
+          type="textarea"
         />
 
         <div class="row q-gutter-sm q-mt-xs q-mb-xl">
           <q-chip
             v-for="type in addressTypes"
             :key="type.value"
+            :icon="type.icon"
+            :icon-selected="type.icon"
+            :outline="form.address_type !== type.value"
             :selected="form.address_type === type.value"
             clickable
             color="primary"
-            :outline="form.address_type !== type.value"
             text-color="surface"
-            :icon="type.icon"
-            :icon-selected="type.icon"
             @click="form.address_type = type.value"
           >
             {{ type.label }}
           </q-chip>
         </div>
-      </div>
+      </q-form>
 
       <div class="address-completion-footer">
         <q-btn
+          class="full-width"
           color="primary-button"
-          :label="$t('auth.confirm_address')"
-          rounded
           padding="14px 16px"
-          class="full-width"
+          rounded
+          :label="$t('auth.confirm_address')"
           :loading="submitting"
           @click="handleConfirm"
         />
@@ -164,48 +236,73 @@
 </template>
 
 <script setup>
-import { ref, computed, onMounted } from 'vue';
-import { useRouter } from 'vue-router';
-import { useQuasar } from 'quasar';
-import { useI18n } from 'vue-i18n';
-import { useRegistrationFlowStore } from 'src/stores/registrationFlow';
-import { useAuth } from 'src/composables/useAuth';
-import { createUserAndClient } from 'src/api/user';
+import { computed, onMounted, ref } from "vue";
+import { createUserAndClient } from "src/api/user";
+import { useAuth } from "src/composables/useAuth";
+import { useI18n } from "vue-i18n";
+import { useInputRules } from "src/composables/useInputRules";
+import { useQuasar } from "quasar";
+import { useRegistrationFlowStore } from "src/stores/registrationFlow";
+import { useRouter } from "vue-router";
 
+const { t } = useI18n();
 const router = useRouter();
 const $q = useQuasar();
-const { t } = useI18n();
-const flowStore = useRegistrationFlowStore();
 const { setAuthDataFromPayload } = useAuth();
+const { inputRules } = useInputRules();
 
+const flowStore = useRegistrationFlowStore();
+
+const addressForm = ref(null);
 const submitting = ref(false);
 
 const form = ref({
-  number: '',
+  document: flowStore.document || "",
+  number: "",
   no_complement: false,
-  complement: '',
-  nickname: '',
-  instructions: '',
-  address_type: 'home',
+  complement: "",
+  nickname: "",
+  instructions: "",
+  address_type: "home",
+  phone: flowStore.phone || "",
 });
 
 const addressTypes = computed(() => [
-  { value: 'home', label: t('auth.address_type_home'), icon: 'mdi-home-outline' },
-  { value: 'commercial', label: t('auth.address_type_commercial'), icon: 'mdi-office-building-outline' },
-  { value: 'other', label: t('auth.address_type_other'), icon: 'mdi-map-marker-outline' },
+  {
+    value: "home",
+    label: t("auth.address_type_home"),
+    icon: "mdi-home-outline",
+  },
+  {
+    value: "commercial",
+    label: t("auth.address_type_commercial"),
+    icon: "mdi-office-building-outline",
+  },
+  {
+    value: "other",
+    label: t("auth.address_type_other"),
+    icon: "mdi-map-marker-outline",
+  },
 ]);
 
 const handleConfirm = async () => {
+  const isValid = await addressForm.value?.validate();
+
+  if (!isValid) return;
+
   if (!form.value.no_complement && !form.value.complement?.trim()) {
-    $q.notify({ type: 'warning', message: t('auth.complement_required') });
+    $q.notify({ type: "warning", message: t("auth.complement_required") });
     return;
   }
 
   submitting.value = true;
+
   try {
     const payload = {
       email: flowStore.email || undefined,
-      phone: flowStore.phone || undefined,
+      phone: form.value.phone,
+      document: form.value.document,
+      name: flowStore.name || undefined,
       code: flowStore.code,
       zip_code: flowStore.confirmedZipCode || undefined,
       address: flowStore.confirmedAddress || undefined,
@@ -216,20 +313,27 @@ const handleConfirm = async () => {
       latitude: flowStore.confirmedLat,
       longitude: flowStore.confirmedLng,
       has_complement: !form.value.no_complement,
-      complement: form.value.no_complement ? null : (form.value.complement || null),
+
+      complement: form.value.no_complement
+        ? null
+        : form.value.complement || null,
+
       nickname: form.value.nickname || null,
       instructions: form.value.instructions || null,
       address_type: form.value.address_type,
     };
 
     const response = await createUserAndClient(payload);
+
     if (response.status >= 200 && response.status < 300) {
       await setAuthDataFromPayload(response.data.payload);
+
       flowStore.clear();
-      router.push({ name: 'DashboardPage' });
+
+      router.push({ name: "DashboardPage" });
     }
   } catch {
-    $q.notify({ type: 'negative', message: t('auth.register_error') });
+    $q.notify({ type: "negative", message: t("auth.register_error") });
   } finally {
     submitting.value = false;
   }
@@ -237,10 +341,10 @@ const handleConfirm = async () => {
 
 onMounted(() => {
   if (!flowStore.hasConfirmedLocation() || !flowStore.hasCredentials()) {
-    router.replace({ name: 'LoginPage' });
+    router.replace({ name: "LoginPage" });
     return;
   }
-  form.value.number = flowStore.confirmedNumber || '';
+  form.value.number = flowStore.confirmedNumber || "";
 });
 </script>
 

+ 44 - 29
src/pages/profile/ProfileEditDialog.vue

@@ -23,20 +23,30 @@
 
         <span
           class="text-subtitle1 text-primary font16 fontbold gradient-diarista"
-          >{{ $t("profile.edit_data") }}</span
         >
+          {{ $t("profile.edit_data") }}
+        </span>
 
         <q-space />
 
         <div style="width: 32px"></div>
       </div>
 
-      <div v-if="loading" class="flex flex-center q-pa-xl">
-        <q-spinner color="primary" size="3em" />
+      <div
+        v-if="loading"
+        class="flex flex-center q-pa-xl"
+      >
+        <q-spinner
+          color="primary"
+          size="3em"
+        />
       </div>
 
       <template v-else>
-        <q-scroll-area class="col" style="height: calc(100vh - 72px)">
+        <q-scroll-area
+          class="col"
+          style="height: calc(100vh - 72px)"
+        >
           <div class="column items-center q-mt-xl q-mb-md">
             <q-avatar
               class="text-h2 shadow-1"
@@ -50,9 +60,9 @@
                 style="object-fit: cover; width: 100%; height: 100%"
               />
 
-              <span v-else>{{
-                form.name ? form.name.charAt(0).toUpperCase() : ""
-              }}</span>
+              <span v-else>
+                {{ form.name ? form.name.charAt(0).toUpperCase() : "" }}
+              </span>
             </q-avatar>
 
             <input
@@ -73,7 +83,10 @@
             />
           </div>
 
-          <q-form ref="formRef" @submit.prevent="onSubmit">
+          <q-form
+            ref="formRef"
+            @submit.prevent="onSubmit"
+          >
             <div class="q-px-xl q-gutter-y-lg">
               <div>
                 <div class="text-grey-8 q-mb-sm font12 fontbold">
@@ -211,6 +224,7 @@
 
 <script setup>
 import { Cookies, useDialogPluginComponent } from "quasar";
+import { getProfileMediaUrl } from "src/helpers/profileMedia";
 import { i18n } from "src/boot/i18n";
 import { onMounted, ref } from "vue";
 import { updateMe } from "src/api/user";
@@ -218,12 +232,12 @@ import { useFormUpdateTracker } from "src/composables/useFormUpdateTracker";
 import { useInputRules } from "src/composables/useInputRules";
 import { useSubmitHandler } from "src/composables/useSubmitHandler";
 
+defineEmits([...useDialogPluginComponent.emits]);
+
 const props = defineProps({
   userData: { type: Object, default: null },
 });
 
-defineEmits([...useDialogPluginComponent.emits]);
-
 const { dialogRef, onDialogOK, onDialogCancel } = useDialogPluginComponent();
 
 const { form, hasUpdatedFields, setUpdateFormAsOriginal } =
@@ -240,11 +254,11 @@ const {
 
 const { inputRules } = useInputRules();
 
-const formRef       = ref(null);
-const fileInputRef  = ref(null);
-const loading       = ref(false);
-const avatarFile    = ref(null);
+const avatarFile = ref(null);
 const avatarPreview = ref(null);
+const fileInputRef = ref(null);
+const formRef = ref(null);
+const loading = ref(false);
 
 const normalizeLocale = (loc) => {
   if (!loc) return "pt";
@@ -262,6 +276,15 @@ const selectedLocale = ref(
   normalizeLocale(i18n.global.locale.value ?? i18n.global.locale),
 );
 
+//
+const fileToBase64 = (file) =>
+  new Promise((resolve, reject) => {
+    const reader = new FileReader();
+    reader.onload = () => resolve(reader.result);
+    reader.onerror = () => reject(reader.error);
+    reader.readAsDataURL(file);
+  });
+
 const onFileSelected = (event) => {
   const file = event.target.files[0];
 
@@ -278,23 +301,15 @@ const onLocaleChange = (val) => {
   Cookies.set("locale", val, { expires: 365, path: "/" });
 };
 
-const fileToBase64 = (file) =>
-  new Promise((resolve, reject) => {
-    const reader = new FileReader();
-    reader.onload = () => resolve(reader.result);
-    reader.onerror = () => reject(reader.error);
-    reader.readAsDataURL(file);
-  });
-
 const onSubmit = async () => {
   const valid = await formRef.value.validate();
 
   if (!valid) return;
 
   const payload = {
-    name:     form.name,
-    email:    form.email,
-    phone:    form.phone,
+    name: form.name,
+    email: form.email,
+    phone: form.phone,
     document: form.document || null,
   };
 
@@ -309,12 +324,12 @@ onMounted(async () => {
   if (props.userData) {
     const data = props.userData;
 
-    form.name     = data.name            || "";
-    form.email    = data.email           || "";
-    form.phone    = data.phone           || "";
+    form.name = data.name || "";
+    form.email = data.email || "";
+    form.phone = data.phone || "";
     form.document = data.client_document || "";
 
-    avatarPreview.value = data.client?.profile_media?.url ?? null;
+    avatarPreview.value = getProfileMediaUrl(data);
 
     setUpdateFormAsOriginal(data);
 

+ 281 - 102
src/pages/profile/ProfilePage.vue

@@ -1,36 +1,82 @@
 <!-- eslint-disable @intlify/vue-i18n/no-raw-text -->
 <template>
   <q-page class="bg-page q-pb-xl">
-    <div class="row items-center q-px-md q-pt-md q-pb-sm bg-white shadow-profile">
+    <div
+      class="row items-center q-px-md q-pt-md q-pb-sm bg-white shadow-profile"
+    >
       <q-space />
-      <span class="text-subtitle1 text-primary font16 fontbold gradient-diarista">{{ $t('profile.title') }}</span>
+
+      <span
+        class="text-subtitle1 text-primary font16 fontbold gradient-diarista"
+      >
+        {{ $t("profile.title") }}
+      </span>
+
       <q-space />
     </div>
 
     <div class="q-px-md q-mt-md">
-      <q-card class="profile-card bg-surface shadow-card card-border" :flat="false">
-        <q-btn flat round dense icon="mdi-share-variant-outline" color="grey-6" class="absolute-top-right q-ma-sm" />
-        
+      <q-card
+        class="profile-card bg-surface shadow-card card-border"
+        :flat="false"
+      >
+        <q-btn
+          class="absolute-top-right q-ma-sm"
+          color="grey-6"
+          dense
+          flat
+          icon="mdi-share-variant-outline"
+          round
+        />
+
         <q-card-section class="column items-center q-pb-md">
-          <q-avatar size="70px" color="indigo-1" text-color="indigo-4" class="shadow-card text-h5">
-            <img v-if="user.client?.profile_media?.url" :src="user.client.profile_media.url" style="object-fit: cover;" />
-            <span v-else>{{ user.name ? user.name.charAt(0).toUpperCase() : '' }}</span>
+          <q-avatar
+            class="shadow-card text-h5"
+            color="indigo-1"
+            size="70px"
+            text-color="indigo-4"
+          >
+            <img
+              v-if="profileMediaUrl"
+              class="profile-image"
+              :src="profileMediaUrl"
+            />
+
+            <span v-else>
+              {{ user.name ? user.name.charAt(0).toUpperCase() : "" }}
+            </span>
           </q-avatar>
-          
-          <div class="fonte-nome-profile q-mt-md text-dark font16 fontbold">{{ user.name || '—' }}</div>
-          <div class="fonte-email-profile text-grey-6 q-my-sm font10 fontmedium">{{ user.email || '—' }}</div>
-          <div class="fonte-telefone-profile text-grey-7 font12 fontmedium">{{ user.phone || '—' }}</div> 
-          
-          <q-btn 
-            outline 
-            no-caps 
+
+          <div class="fonte-nome-profile q-mt-md text-dark font16 fontbold">
+            {{ user.name || "—" }}
+          </div>
+
+          <div
+            class="fonte-email-profile text-grey-6 q-my-sm font10 fontmedium"
+          >
+            {{ user.email || "—" }}
+          </div>
+
+          <div class="fonte-telefone-profile text-grey-7 font12 fontmedium">
+            {{ user.phone || "—" }}
+          </div>
+
+          <q-btn
+            class="full-width q-mt-sm btn-edit-profile text-text"
+            no-caps
+            outline
             padding="6px 16px"
-            class="full-width q-mt-sm btn-edit-profile text-text" 
             @click="openEditProfile"
           >
             <div class="row items-center q-gutter-x-sm">
-              <q-icon name="mdi-account-outline" size="18px" />
-              <span class="font10 fontbold">{{ $t('profile.edit_profile') }}</span>
+              <q-icon
+                name="mdi-account-outline"
+                size="18px"
+              />
+
+              <span class="font10 fontbold">
+                {{ $t("profile.edit_profile") }}
+              </span>
             </div>
           </q-btn>
         </q-card-section>
@@ -38,152 +84,284 @@
     </div>
 
     <div class="q-mt-md q-px-md column">
-      <div class="menu-item row items-center no-wrap cursor-pointer q-py-sm" @click="openPaymentsDialog">
+      <div
+        class="menu-item row items-center no-wrap cursor-pointer q-py-sm"
+        @click="openPaymentsDialog"
+      >
         <div class="column">
-          <span class="menu-title gradient-diarista font16 fontbold">{{ $t('profile.payments.title') }}</span>
-          <span class="text-text font12 fontregular">{{ $t('profile.payments.description') }}</span>
+          <span class="menu-title gradient-diarista font16 fontbold">
+            {{ $t("profile.payments.title") }}
+          </span>
+
+          <span class="text-text font12 fontregular">
+            {{ $t("profile.payments.description") }}
+          </span>
         </div>
-        <q-space/>
-        <q-icon name="mdi-chevron-right" color="primary" size="md" />
+
+        <q-space />
+
+        <q-icon
+          color="primary"
+          name="mdi-chevron-right"
+          size="md"
+        />
       </div>
 
-      <div class="menu-item row items-center no-wrap cursor-pointer q-py-sm" @click="openFavoritesDialog">
+      <div
+        class="menu-item row items-center no-wrap cursor-pointer q-py-sm"
+        @click="openFavoritesDialog"
+      >
         <div class="column">
-          <span class="menu-title gradient-diarista font16 fontbold">{{ $t('profile.favorites.title') }}</span>
-          <span class="text-text font12 fontregular">{{ $t('profile.favorites.description') }}</span>
+          <span class="menu-title gradient-diarista font16 fontbold">
+            {{ $t("profile.favorites.title") }}
+          </span>
+
+          <span class="text-text font12 fontregular">
+            {{ $t("profile.favorites.description") }}
+          </span>
         </div>
-        <q-space/>
-        <q-icon name="mdi-chevron-right" color="primary" size="md" />
+
+        <q-space />
+
+        <q-icon
+          color="primary"
+          name="mdi-chevron-right"
+          size="md"
+        />
       </div>
 
-      <div class="menu-item row items-center no-wrap cursor-pointer q-py-sm" @click="openAddressDialog">
+      <div
+        class="menu-item row items-center no-wrap cursor-pointer q-py-sm"
+        @click="openAddressDialog"
+      >
         <div class="column">
-          <span class="menu-title gradient-diarista font16 fontbold">{{ $t('profile.address.title') }}</span>
-          <span class="text-text font12 fontregular">{{ $t('profile.address.description') }}</span>
+          <span class="menu-title gradient-diarista font16 fontbold">
+            {{ $t("profile.address.title") }}
+          </span>
+
+          <span class="text-text font12 fontregular">
+            {{ $t("profile.address.description") }}
+          </span>
         </div>
-        <q-space/>
-        <q-icon name="mdi-chevron-right" color="primary" size="md" />
+
+        <q-space />
+
+        <q-icon
+          color="primary"
+          name="mdi-chevron-right"
+          size="md"
+        />
       </div>
 
-      <div class="menu-item row items-center no-wrap cursor-pointer q-py-sm" @click="openHelpDialog">
+      <div
+        class="menu-item row items-center no-wrap cursor-pointer q-py-sm"
+        @click="openHelpDialog"
+      >
         <div class="column">
-          <span class="menu-title gradient-diarista font16 fontbold">{{ $t('profile.help.title') }}</span>
-          <span class="text-text font12 fontregular">{{ $t('profile.help.description') }}</span>
+          <span class="menu-title gradient-diarista font16 fontbold">
+            {{ $t("profile.help.title") }}
+          </span>
+
+          <span class="text-text font12 fontregular">
+            {{ $t("profile.help.description") }}
+          </span>
         </div>
-        <q-space/>
-        <q-icon name="mdi-chevron-right" color="primary" size="md" />
+
+        <q-space />
+
+        <q-icon
+          color="primary"
+          name="mdi-chevron-right"
+          size="md"
+        />
       </div>
 
-      <div class="menu-item row items-center no-wrap cursor-pointer q-py-sm" @click="openPrivacyDialog">
+      <div
+        class="menu-item row items-center no-wrap cursor-pointer q-py-sm"
+        @click="openPrivacyDialog"
+      >
         <div class="column">
-          <span class="menu-title gradient-diarista font16 fontbold">{{ $t('profile.privacy.title') }}</span>
-          <span class="text-text font12 fontregular">{{ $t('profile.privacy.description') }}</span>
+          <span class="menu-title gradient-diarista font16 fontbold">
+            {{ $t("profile.privacy.title") }}
+          </span>
+
+          <span class="text-text font12 fontregular">
+            {{ $t("profile.privacy.description") }}
+          </span>
         </div>
-        <q-space/>
-        <q-icon name="mdi-chevron-right" color="primary" size="md" />
+
+        <q-space />
+
+        <q-icon
+          color="primary"
+          name="mdi-chevron-right"
+          size="md"
+        />
       </div>
 
-      <div class="menu-item row items-center no-wrap cursor-pointer q-py-sm" @click="openPrivacyPolicy">
+      <div
+        class="menu-item row items-center no-wrap cursor-pointer q-py-sm"
+        @click="openPrivacyPolicy"
+      >
         <div class="column">
-          <span class="menu-title gradient-diarista font16 fontbold">{{ $t('profile.privacy_policy.title') }}</span>
-          <span class="text-text font12 fontregular">{{ $t('profile.privacy_policy.description') }}</span>
+          <span class="menu-title gradient-diarista font16 fontbold">
+            {{ $t("profile.privacy_policy.title") }}
+          </span>
+
+          <span class="text-text font12 fontregular">
+            {{ $t("profile.privacy_policy.description") }}
+          </span>
         </div>
-        <q-space/>
-        <q-icon name="mdi-chevron-right" color="primary" size="md" />
+
+        <q-space />
+
+        <q-icon
+          color="primary"
+          name="mdi-chevron-right"
+          size="md"
+        />
       </div>
 
-      <div class="menu-item row items-center no-wrap cursor-pointer q-py-sm" @click="openSupportPage">
+      <div
+        class="menu-item row items-center no-wrap cursor-pointer q-py-sm"
+        @click="openSupportPage"
+      >
         <div class="column">
-          <span class="menu-title gradient-diarista font16 fontbold">{{ $t('profile.support_page.title') }}</span>
-          <span class="text-text font12 fontregular">{{ $t('profile.support_page.description') }}</span>
+          <span class="menu-title gradient-diarista font16 fontbold">
+            {{ $t("profile.support_page.title") }}
+          </span>
+
+          <span class="text-text font12 fontregular">
+            {{ $t("profile.support_page.description") }}
+          </span>
         </div>
-        <q-space/>
-        <q-icon name="mdi-chevron-right" color="primary" size="md" />
+
+        <q-space />
+
+        <q-icon
+          color="primary"
+          name="mdi-chevron-right"
+          size="md"
+        />
       </div>
 
-      <q-separator class="q-my-sm bg-grey-3" inset />
+      <q-separator
+        class="q-my-sm bg-grey-3"
+        inset
+      />
 
-      <div class="menu-item row items-center no-wrap cursor-pointer q-py-sm" @click="handleLogout">
+      <div
+        class="menu-item row items-center no-wrap cursor-pointer q-py-sm"
+        @click="handleLogout"
+      >
         <div class="column">
-          <span class="menu-title text-text font16 fontbold">{{ $t('profile.logout.title') }}</span>
-          <span class="text-text font12 fontregular">{{ $t('profile.logout.description') }}</span>
+          <span class="menu-title text-text font16 fontbold">
+            {{ $t("profile.logout.title") }}
+          </span>
+
+          <span class="text-text font12 fontregular">
+            {{ $t("profile.logout.description") }}
+          </span>
         </div>
-        <q-space/>
-        <q-icon name="mdi-chevron-right" color="primary" size="md" />
+
+        <q-space />
+
+        <q-icon
+          color="primary"
+          name="mdi-chevron-right"
+          size="md"
+        />
       </div>
     </div>
   </q-page>
 </template>
 
 <script setup>
-import { ref, onMounted } from 'vue';
-import { useQuasar } from 'quasar';
-import { Browser } from '@capacitor/browser';
-import { useAuth } from 'src/composables/useAuth';
-import { getUser } from 'src/api/user';
-import { userStore } from 'src/stores/user';
-import ProfileEditDialog from './ProfileEditDialog.vue';
-import ProfileAddressDialog from 'src/components/profile/ProfileAddressDialog.vue';
-import ProfilePaymentsDialog from 'src/components/profile/ProfilePaymentsDialog.vue';
-import ProfileFavoritesDialog from 'src/components/profile/ProfileFavoritesDialog.vue';
-import ProfileHelpDialog from 'src/components/profile/ProfileHelpDialog.vue';
-import ProfilePrivacyDialog from 'src/components/profile/ProfilePrivacyDialog.vue';
-import { useRouter } from 'vue-router';
-
-const PRIVACY_POLICY_URL = 'https://politicas.softpar.inf.br/politicas/politicaDiaristaCliente.html';
-const SUPPORT_PAGE_URL   = 'https://politicas.softpar.inf.br/suportes/suporteDiaristaCliente.html';
+import { Browser } from "@capacitor/browser";
+import { computed, onMounted, ref } from "vue";
+import { getProfileMediaUrl } from "src/helpers/profileMedia";
+import { getUser } from "src/api/user";
+import { useAuth } from "src/composables/useAuth";
+import { useQuasar } from "quasar";
+import { useRouter } from "vue-router";
+import { userStore } from "src/stores/user";
+
+import ProfileAddressDialog from "src/components/profile/ProfileAddressDialog.vue";
+import ProfileEditDialog from "./ProfileEditDialog.vue";
+import ProfileFavoritesDialog from "src/components/profile/ProfileFavoritesDialog.vue";
+import ProfileHelpDialog from "src/components/profile/ProfileHelpDialog.vue";
+import ProfilePaymentsDialog from "src/components/profile/ProfilePaymentsDialog.vue";
+import ProfilePrivacyDialog from "src/components/profile/ProfilePrivacyDialog.vue";
 
+const router = useRouter();
 const $q = useQuasar();
+const { logout } = useAuth();
+
 const store = userStore();
 
-const { logout } = useAuth();
-const router = useRouter();
 const user = ref({
-  name: '',
-  email: '',
-  phone: ''
+  email: "",
+  name: "",
+  phone: "",
 });
 
+const profileMediaUrl = computed(() => getProfileMediaUrl(user.value));
+
+const PRIVACY_POLICY_URL =
+  "https://politicas.softpar.inf.br/politicas/politicaDiaristaCliente.html";
+const SUPPORT_PAGE_URL =
+  "https://politicas.softpar.inf.br/suportes/suporteDiaristaCliente.html";
+
+const handleLogout = async () => {
+  await logout();
+
+  router.push("/login");
+};
+
+const openAddressDialog = () => {
+  $q.dialog({
+    component: ProfileAddressDialog,
+  });
+};
+
 const openEditProfile = () => {
   $q.dialog({
     component: ProfileEditDialog,
     componentProps: {
-      userData: user.value
-    }
+      userData: user.value,
+    },
   }).onOk((updatedUser) => {
     user.value = { ...user.value, ...updatedUser };
+
     store.setUser({ ...store.user, ...updatedUser });
-    getUser().then(data => { user.value = data; });
-  });
-};
 
-const openPaymentsDialog = () => {
-  $q.dialog({
-    component: ProfilePaymentsDialog
+    getUser().then((data) => {
+      user.value = data;
+    });
   });
 };
 
 const openFavoritesDialog = () => {
   $q.dialog({
-    component: ProfileFavoritesDialog
+    component: ProfileFavoritesDialog,
   });
 };
 
-const openAddressDialog = () => {
+const openHelpDialog = () => {
   $q.dialog({
-    component: ProfileAddressDialog
+    component: ProfileHelpDialog,
   });
 };
 
-const openHelpDialog = () => {
+const openPaymentsDialog = () => {
   $q.dialog({
-    component: ProfileHelpDialog
+    component: ProfilePaymentsDialog,
   });
 };
 
 const openPrivacyDialog = () => {
   $q.dialog({
-    component: ProfilePrivacyDialog
+    component: ProfilePrivacyDialog,
   });
 };
 
@@ -195,17 +373,12 @@ const openSupportPage = async () => {
   await Browser.open({ url: SUPPORT_PAGE_URL });
 };
 
-const handleLogout = async () => {
-  await logout();
-  router.push('/login');
-};
-
 onMounted(async () => {
   try {
     const data = await getUser();
     user.value = data;
   } catch (error) {
-    console.error('Erro ao carregar perfil:', error);
+    console.error("Erro ao carregar perfil:", error);
   }
 });
 </script>
@@ -215,6 +388,12 @@ onMounted(async () => {
   position: relative;
 }
 
+.profile-image {
+  height: 100%;
+  object-fit: cover;
+  width: 100%;
+}
+
 .btn-edit-profile {
   border-radius: 6px;
 }
@@ -222,7 +401,7 @@ onMounted(async () => {
 .menu-item {
   transition: background 0.3s;
   &:active {
-    background: rgba(0,0,0,0.05);
+    background: rgba(0, 0, 0, 0.05);
   }
 }
 

+ 0 - 529
src/pages/schedule-cart/ScheduleCartPage.vue

@@ -1,529 +0,0 @@
-<template>
-  <q-page class="schedule-cart-page bg-page q-pb-xl">
-    <div class="row items-center q-px-md q-pt-md q-pb-sm bg-white shadow-cart">
-      <q-btn
-        color="primary"
-        dense
-        flat
-        icon="mdi-chevron-left"
-        round @click="router.back()"
-      />
-
-      <div class="col text-center font16 fontbold gradient-diarista text-primary">
-        {{ $t('schedule_cart.title') }}
-      </div>
-
-      <div style="width: 36px"></div>
-    </div>
-
-    <div v-if="cart.items.length === 0" class="q-px-md q-pt-md">
-      <q-card :flat="false" class="empty-card bg-surface shadow-card card-border">
-        <q-card-section class="column items-center text-center q-pa-lg">
-          <q-icon
-            color="grey-5"
-            name="mdi-cart-off"
-            size="42px"
-          />
-
-          <span class="font14 fontbold text-text q-mt-sm">
-            {{ $t('schedule_cart.empty_title') }}
-          </span>
-
-          <span class="font12 text-grey-7 q-mt-xs">
-            {{ $t('schedule_cart.empty_message') }}
-          </span>
-
-          <q-btn
-            class="q-mt-md"
-            color="primary"
-            no-caps
-            padding="8px 18px"
-            rounded
-            unelevated
-            :label="$t('schedule_cart.view_favorites')"
-            @click="router.push({ name: 'ProfilePage' })"
-          />
-        </q-card-section>
-      </q-card>
-
-      <q-card v-if="cart.lastCreatedGroup" :flat="false" class="created-card bg-surface shadow-card card-border q-mt-md">
-        <q-card-section class="q-pa-md">
-          <div class="row items-center no-wrap">
-            <q-icon
-              color="positive"
-              name="mdi-check-circle-outline"
-              size="24px"
-            />
-
-            <div class="column q-ml-sm">
-              <span class="font14 fontbold text-text">
-                {{ $t('schedule_cart.last_request_title') }}
-              </span>
-
-              <span class="font12 text-grey-7">
-                {{ $t('schedule_cart.created_count', { count: cart.lastCreatedGroup.schedule_ids.length }) }}
-              </span>
-            </div>
-          </div>
-        </q-card-section>
-      </q-card>
-    </div>
-
-    <template v-else>
-      <div class="q-px-md q-pt-md column q-gutter-y-sm">
-        <div class="cart-discount-banner row items-center no-wrap q-pa-sm">
-          <q-icon
-            color="positive"
-            name="mdi-sale-outline"
-            size="20px"
-          />
-
-          <span class="font12 fontbold text-positive q-ml-sm">
-            {{ cartDiscountMessage }}
-          </span>
-        </div>
-
-        <q-card
-          v-for="(item, index) in cart.items"
-          :key="item.local_id"
-          :flat="false"
-          class="cart-item-card bg-surface shadow-card card-border"
-        >
-          <q-card-section class="row no-wrap q-pa-md">
-            <q-avatar color="indigo-1" size="44px" text-color="indigo-4">
-              <span>
-                {{ getProviderInitial(item) }}
-              </span>
-            </q-avatar>
-
-            <div class="column col q-ml-sm">
-              <span class="font14 fontbold text-text">
-                {{ item.provider_name }}
-              </span>
-
-              <span class="font12 text-grey-7">
-                {{ formatCartDate(item.date) }}
-              </span>
-
-              <span class="font12 text-grey-7">
-                {{ $t('schedule_cart.time_range', { start: item.start_time, end: item.end_time }) }}
-              </span>
-
-              <div class="row items-center q-gutter-x-sm q-mt-xs">
-                <q-chip
-                  class="font10"
-                  color="purple-1"
-                  dense
-                  square
-                  text-color="primary"
-                >
-                  {{ $t('schedule_cart.hours_short', { hours: item.period_type }) }}
-                </q-chip>
-
-                <q-chip class="font10" color="grey-2" dense square text-color="grey-8">
-                  {{ item.offers_meal ? $t('schedule_cart.with_meal') : $t('schedule_cart.without_meal') }}
-                </q-chip>
-              </div>
-
-              <div class="item-prices q-mt-sm">
-                <div v-if="!hasCartDiscount" class="row items-center no-wrap">
-                  <span class="font11 fontbold text-primary">{{ $t('schedule_cart.pix_total') }}</span>
-
-                  <q-space />
-
-                  <span class="font12 fontbold text-primary">
-                    {{ formatCurrencyValue(getStandardPixItemTotal(item)) }}
-                  </span>
-                </div>
-
-                <div v-if="!hasCartDiscount" class="row items-center no-wrap q-mt-xs">
-                  <span class="font11 text-grey-7">
-                    {{ $t('schedule_cart.credit_card_total') }}
-                  </span>
-
-                  <q-space />
-
-                  <span class="font12 fontbold text-text">
-                    {{ formatCurrencyValue(getCreditCardItemTotal(item)) }}
-                  </span>
-                </div>
-
-                <div v-if="hasCartDiscount" class="row items-center no-wrap q-mt-xs">
-                  <span class="font11 fontbold text-positive">
-                    {{ $t('schedule_cart.cart_discount_total') }}
-                  </span>
-
-                  <q-space />
-
-                  <span class="font12 fontbold text-positive">
-                    {{ formatCurrencyValue(getCartDiscountItemTotal(item)) }}
-                  </span>
-                </div>
-              </div>
-            </div>
-
-            <q-btn
-              color="negative"
-              dense
-              flat
-              icon="mdi-delete-outline"
-              round
-              @click="cart.removeItem(index)"
-            />
-          </q-card-section>
-        </q-card>
-      </div>
-
-      <div class="q-px-md q-pt-md">
-        <q-card :flat="false" class="checkout-card bg-surface shadow-card card-border">
-          <q-card-section class="q-pa-md">
-            <div class="row items-center no-wrap q-mb-sm">
-              <span class="font14 fontbold text-primary">
-                {{ $t('schedule_cart.summary') }}
-              </span>
-
-              <q-space />
-            </div>
-
-            <div class="summary-row row items-center no-wrap q-py-xs">
-              <span class="font12 text-grey-7">
-                {{ $t('schedule_cart.provider') }}
-              </span>
-
-              <q-space />
-
-              <span class="font12 fontbold text-text text-right">
-                {{ providerName }}
-              </span>
-            </div>
-
-            <div class="summary-row row items-center no-wrap q-py-xs">
-              <span class="font12 text-grey-7">
-                {{ $t('schedule_cart.address') }}
-              </span>
-
-              <q-space />
-
-              <span class="font12 fontbold text-text text-right">
-                {{ primaryAddressLabel }}
-              </span>
-            </div>
-
-            <div class="summary-row row items-center no-wrap q-py-xs">
-              <span class="font12 text-grey-7">
-                {{ $t('schedule_cart.quantity') }}
-              </span>
-
-              <q-space />
-
-              <span class="font12 fontbold text-text">
-                {{ $t('schedule_cart.schedule_count', { count: cart.items.length }) }}
-              </span>
-            </div>
-
-            <div v-if="!hasCartDiscount" class="summary-row row items-center no-wrap q-py-xs">
-              <span class="font12 fontbold text-primary">
-                {{ $t('schedule_cart.pix_total') }}
-              </span>
-
-              <q-space />
-
-              <span class="font14 fontbold text-primary">
-                {{ formatCurrencyValue(pixTotalAmount) }}
-              </span>
-            </div>
-
-            <div v-if="hasCartDiscount" class="summary-row row items-center no-wrap q-py-xs">
-              <span class="font12 fontbold text-positive">
-                {{ $t('schedule_cart.cart_discount_total') }}
-              </span>
-
-              <q-space />
-
-              <span class="font14 fontbold text-positive">
-                {{ formatCurrencyValue(cartDiscountTotalAmount) }}
-              </span>
-            </div>
-
-            <div v-if="pixDiscountAmount > 0" class="summary-discount row items-center no-wrap q-py-xs q-px-sm q-mt-sm">
-              <q-icon color="positive" name="mdi-tag-outline" size="18px" />
-
-              <span class="font12 fontbold text-positive q-ml-xs">
-                {{ $t('schedule_cart.cart_fee_discount', { value: formatPrice(pixDiscountAmount) }) }}
-              </span>
-            </div>
-          </q-card-section>
-
-          <q-card-actions class="q-px-md q-pb-md">
-            <q-btn
-              class="full-width"
-              color="primary"
-              no-caps
-              padding="10px 16px"
-              rounded
-              unelevated
-              :disable="addressLoading || !platformFeesReady"
-              :label="$t('schedule_cart.close_cart')"
-              :loading="submitting"
-              @click="submitCart"
-            />
-          </q-card-actions>
-        </q-card>
-      </div>
-    </template>
-  </q-page>
-</template>
-
-<script setup>
-import { computed, onMounted, ref } from "vue";
-import { useRouter } from "vue-router";
-import { useI18n } from "vue-i18n";
-import { useQuasar } from "quasar";
-import { getAddresses } from "src/api/address";
-import { createScheduleCart } from "src/api/cart";
-import { usePaymentPlatformFees } from "src/composables/usePaymentPlatformFees";
-import { useScheduleCartStore } from "src/stores/scheduleCart";
-import { userStore } from "src/stores/user";
-
-const router = useRouter();
-const $q     = useQuasar();
-const { t }  = useI18n();
-
-const cart  = useScheduleCartStore();
-const store = userStore();
-
-const { platformFees, loadPlatformFees } = usePaymentPlatformFees();
-
-const primaryAddress = ref(null);
-const addressLoading = ref(false);
-const submitting     = ref(false);
-
-const cartDiscountMessage = computed(() => {
-  if (hasCartDiscount.value) {
-    return t("schedule_cart.cart_discount_applied");
-  }
-
-  return t("schedule_cart.cart_discount_hint", { count: missingSchedulesForDiscount.value });
-});
-
-const cartDiscountTotalAmount = computed(() => roundNullableMoney(sumCartItems(getCartDiscountItemTotal)));
-
-const hasCartDiscount = computed(() => cart.items.length >= 3);
-
-const missingSchedulesForDiscount = computed(() => Math.max(3 - cart.items.length, 0));
-
-const platformFeesReady = computed(() =>
-  isValidFee(platformFees.value.pix)         &&
-  isValidFee(platformFees.value.credit_card) &&
-  (!hasCartDiscount.value || isValidFee(platformFees.value.cart_min_3_schedules))
-);
-
-const pixDiscountAmount = computed(() => {
-  if (!hasCartDiscount.value) return 0;
-
-  return roundNullableMoney(sumCartItems((item) => {
-    const standardPixItemTotal   = getStandardPixItemTotal(item);
-    const discountedPixItemTotal = getCartDiscountItemTotal(item);
-
-    if (standardPixItemTotal === null || discountedPixItemTotal === null) return null;
-
-    return standardPixItemTotal - discountedPixItemTotal;
-  })) ?? 0;
-});
-
-const pixTotalAmount = computed(() => roundNullableMoney(sumCartItems(getStandardPixItemTotal)));
-
-const primaryAddressLabel = computed(() => {
-  if (addressLoading.value)  return t("schedule_cart.loading");
-  if (!primaryAddress.value) return t("schedule_cart.no_primary_address");
-
-  return [
-    primaryAddress.value.address,
-    primaryAddress.value.number,
-    primaryAddress.value.district,
-  ].filter(Boolean).join(", ");
-});
-
-const providerName = computed(() => {
-  const providerNames = [...new Set(cart.items.map((item) => item.provider_name).filter(Boolean))];
-
-  if (providerNames.length === 0) return t("schedule_cart.unknown");
-  if (providerNames.length === 1) return providerNames[0];
-
-  return t("schedule_cart.multiple_providers", { count: providerNames.length });
-});
-
-const formatCartDate = (value) => {
-  const parsed = new Date(`${normalizeDate(value)}T12:00:00`);
-
-  return new Intl.DateTimeFormat("pt-BR", { day: "2-digit", month: "2-digit", year: "numeric" }).format(parsed);
-};
-
-const formatCurrencyValue = (value) => {
-  if (value === null || value === undefined || !Number.isFinite(Number(value))) {
-    return t("schedule_cart.loading");
-  }
-
-  return t("schedule_cart.currency_value", { value: formatPrice(value) });
-};
-
-const formatPrice = (value) => {
-  if (value === null || value === undefined || !Number.isFinite(Number(value))) {
-    return t("schedule_cart.loading");
-  }
-
-  return Number(value ?? 0).toLocaleString("pt-BR", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
-};
-
-const getCartDiscountItemTotal = (item) => getTotalWithFee(item, platformFees.value.cart_min_3_schedules);
-
-const getCreditCardItemTotal = (item) => getTotalWithFee(item, platformFees.value.credit_card);
-
-const getProviderInitial = (item) => item.provider_name?.slice(0, 1).toUpperCase() ?? t("schedule_cart.provider_initial");
-
-const getStandardPixItemTotal = (item) => getTotalWithFee(item, platformFees.value.pix);
-
-const getTotalWithFee = (item, fee) => {
-  if (!isValidFee(fee)) return null;
-
-  return roundMoney(Number(item.base_amount ?? 0) * (1 + Number(fee)));
-};
-
-const isValidFee = (fee) => fee !== null && fee !== undefined && Number.isFinite(Number(fee));
-
-const loadPrimaryAddress = async () => {
-  const clientId = store.user?.client_id ?? store.user?.client?.id;
-
-  if (!clientId) return;
-
-  addressLoading.value = true;
-
-  try {
-    const addresses = await getAddresses("client", clientId);
-
-    primaryAddress.value = (addresses ?? []).find((address) => address.is_primary) ?? null;
-  } catch {
-    primaryAddress.value = null;
-  } finally {
-    addressLoading.value = false;
-  }
-};
-
-const normalizeDate = (value) => String(value ?? "").replace(/\//g, "-").slice(0, 10);
-
-const roundMoney = (value) => Number(Number(value ?? 0).toFixed(2));
-
-const roundNullableMoney = (value) => {
-  if (value === null || value === undefined || !Number.isFinite(Number(value))) return null;
-
-  return roundMoney(value);
-};
-
-const submitCart = async () => {
-  if (!cart.items.length || submitting.value) return;
-
-  if (!primaryAddress.value) {
-    $q.notify({ type: "warning", message: t("schedule_cart.no_primary_address_notify") });
-
-    return;
-  }
-
-  const payload = {
-    client_id:     store.user.client_id ?? store.user?.client?.id,
-    address_id:    primaryAddress.value.id,
-    schedule_type: "default",
-
-    schedules: cart.items.map((item) => ({
-      provider_id:  item.provider_id,
-      date:         item.date,
-      period_type:  item.period_type,
-      start_time:   item.start_time,
-      end_time:     item.end_time,
-      total_amount: item.base_amount,
-      offers_meal:  item.offers_meal,
-    })),
-  };
-
-  submitting.value = true;
-
-  try {
-    const createdCart = await createScheduleCart(payload);
-
-    const createdSchedules = createdCart?.schedules ?? [];
-
-    const scheduleIds = createdCart?.schedule_ids ?? createdSchedules.map((schedule) => schedule.id);
-
-    cart.setLastCreatedGroup({
-      local_group_id: createdCart?.id ?? `cart-${Date.now()}`,
-      cart_id:        createdCart?.id ?? null,
-      schedule_ids:   scheduleIds,
-      schedules:      createdSchedules,
-    });
-
-    cart.clear();
-
-    $q.notify({ type: "positive", message: t("schedule_cart.cart_closed_success") });
-  } catch (error) {
-    const message = error?.response?.data?.message ?? error?.message ?? t("schedule_cart.submit_error");
-
-    $q.notify({ type: "negative", message });
-  } finally {
-    submitting.value = false;
-  }
-};
-
-const sumCartItems = (callback) => {
-  let total = 0;
-
-  for (const item of cart.items) {
-    const itemTotal = callback(item);
-
-    if (itemTotal === null) return null;
-
-    total += itemTotal;
-  }
-
-  return total;
-};
-
-onMounted(() => {
-  loadPrimaryAddress();
-  loadPlatformFees().catch(() => {});
-});
-</script>
-
-<style scoped lang="scss">
-.schedule-cart-page {
-  min-height: 100%;
-}
-
-.shadow-cart {
-  box-shadow: 0 1px 8px rgba(0, 0, 0, 0.1);
-}
-
-.cart-item-card,
-.checkout-card,
-.empty-card,
-.created-card {
-  border-radius: 8px;
-}
-
-.summary-row {
-  border-top: 1px solid rgba(0, 0, 0, 0.05);
-}
-
-.item-prices {
-  max-width: 220px;
-}
-
-.cart-discount-banner {
-  background: rgba(34, 197, 94, 0.1);
-  border: 1px solid rgba(34, 197, 94, 0.22);
-  border-radius: 8px;
-}
-
-.summary-discount {
-  background: rgba(34, 197, 94, 0.12);
-  border-radius: 8px;
-}
-</style>

+ 296 - 125
src/pages/search/SearchPage.vue

@@ -1,33 +1,53 @@
 <!-- eslint-disable @intlify/vue-i18n/no-raw-text -->
 <template>
   <q-page class="bg-page">
+    <div
+      class="row items-center q-px-md q-pt-md q-pb-sm bg-white shadow-search"
+    >
+      <q-btn
+        color="primary"
+        dense
+        flat
+        icon="mdi-chevron-left"
+        round
+        @click="router.back()"
+      />
 
-    <div class="row items-center q-px-md q-pt-md q-pb-sm bg-white shadow-search">
-      <q-btn flat round dense icon="mdi-chevron-left" color="primary" @click="router.back()" />
-      <div class="col text-center font16 fontbold gradient-diarista text-primary">
-        {{ $t('search_page.title') }}
+      <div
+        class="col text-center font16 fontbold gradient-diarista text-primary"
+      >
+        {{ $t("search_page.title") }}
       </div>
+
       <div style="width: 36px" />
     </div>
 
     <div class="q-px-md q-pt-md q-pb-sm">
-      <q-card class="custom-schedule-card bg-surface shadow-card q-pa-sm" :flat="false">
+      <q-card
+        class="custom-schedule-card bg-surface shadow-card q-pa-sm"
+        :flat="false"
+      >
         <q-card-section class="row items-center no-wrap q-pa-sm q-gutter-x-sm">
           <span class="col text-text font12 fontmedium fonte-hint">
-            {{ $t('search_page.custom_schedule_description') }}
+            {{ $t("search_page.custom_schedule_description") }}
           </span>
+
           <q-btn
+            class="font12 fontmedium custom-schedule-btn card-border"
             color="secondary"
             no-caps
-            unelevated
             padding="8px 16px"
-            class="font12 fontmedium custom-schedule-btn card-border"
+            unelevated
             @click="router.push({ name: 'SobMedidaPage' })"
           >
             <template #default>
               <div class="column items-center q-gutter-y-xs">
-                <q-icon name="mdi-scissors-cutting" size="16px" />
-                <span>{{ $t('search_page.custom_schedule_btn') }}</span>
+                <q-icon
+                  name="mdi-scissors-cutting"
+                  size="16px"
+                />
+
+                <span>{{ $t("search_page.custom_schedule_btn") }}</span>
               </div>
             </template>
           </q-btn>
@@ -38,94 +58,199 @@
     <div class="row items-center q-px-md q-py-md q-gutter-x-sm">
       <q-input
         v-model="searchName"
-        :placeholder="$t('search_page.search_placeholder')"
-        outlined
-        rounded
-        dense
+        class="col bg-white search-input"
         clearable
         debounce="400"
-        class="col bg-white search-input"
+        dense
         input-class="text-text"
+        outlined
+        rounded
+        :placeholder="$t('search_page.search_placeholder')"
         @update:model-value="onNameChange"
       >
         <template #append>
-          <q-icon name="mdi-magnify" color="grey-5" />
+          <q-icon
+            color="grey-5"
+            name="mdi-magnify"
+          />
         </template>
       </q-input>
+
       <q-btn
-        flat round dense
-        icon="mdi-tune-variant"
+        :class="{ 'filter-active': activeSort || activeDate }"
         color="grey-6"
+        dense
+        flat
+        icon="mdi-tune-variant"
+        round
         size="md"
-        :class="{ 'filter-active': activeSort || activeDate }"
         @click="openFilterDialog"
       />
     </div>
 
     <div class="row items-center justify-between no-wrap q-px-md q-pb-sm">
-      <div class="dashboard-section-title font16 fontbold gradient-diarista">{{ $t('search_page.choose_provider') }}</div>
+      <div class="dashboard-section-title font16 fontbold gradient-diarista">
+        {{ $t("search_page.choose_provider") }}
+      </div>
+
       <div class="row items-center no-wrap text-text">
-        <q-btn flat dense round icon="mdi-chevron-left" color="text" size="sm" @click="setPeriodTypePrevious" />
+        <q-btn
+          color="text"
+          dense
+          flat
+          icon="mdi-chevron-left"
+          round
+          size="sm"
+          @click="setPeriodTypePrevious"
+        />
+
         <span class="font10 fontbold">{{ periodLabel }}</span>
-        <q-btn flat dense round icon="mdi-chevron-right" color="text" size="sm" @click="setPeriodTypeNext" />
+
+        <q-btn
+          color="text"
+          dense
+          flat
+          icon="mdi-chevron-right"
+          round
+          size="sm"
+          @click="setPeriodTypeNext"
+        />
       </div>
     </div>
 
-    <div v-if="loading" class="row items-center justify-center q-py-xl">
-      <q-spinner-dots color="primary" size="40px" />
+    <div
+      v-if="loading"
+      class="row items-center justify-center q-py-xl"
+    >
+      <q-spinner-dots
+        color="primary"
+        size="40px"
+      />
     </div>
 
     <template v-else>
-      <div v-if="sortedProviders.length === 0" class="text-center text-grey-6 q-px-md q-py-lg text-body2">
-        {{ $t('search_page.no_results') }}
+      <div
+        v-if="sortedProviders.length === 0"
+        class="text-center text-grey-6 q-px-md q-py-lg text-body2"
+      >
+        {{ $t("search_page.no_results") }}
       </div>
 
-      <div v-else class="column q-px-md q-pb-xl">
+      <div
+        v-else
+        class="column q-px-md q-pb-xl"
+      >
         <q-card
           v-for="p in sortedProviders"
           :key="p.provider_id"
-          class="card-border bg-page text-text q-mb-sm"
           :flat="false"
+          class="card-border bg-page text-text q-mb-sm"
         >
           <q-card-section class="row no-wrap q-pa-sm">
             <div class="row no-wrap full-width">
               <div class="col-2">
-                <q-avatar :style="p.profile_media_url ? {} : avatarColors[p.provider_id % avatarColors.length]">
-                  <img v-if="p.profile_media_url" :src="p.profile_media_url" style="object-fit: cover; width: 100%; height: 100%;" />
-                  <span v-else>{{ getFirstName(p.provider_name)?.slice(0, 1).toUpperCase() ?? '—' }}</span>
+                <q-avatar
+                  :style="
+                    p.profile_media_url
+                      ? {}
+                      : avatarColors[p.provider_id % avatarColors.length]
+                  "
+                >
+                  <img
+                    v-if="p.profile_media_url"
+                    :src="p.profile_media_url"
+                    style="object-fit: cover; width: 100%; height: 100%"
+                  />
+
+                  <span v-else>{{
+                    getFirstName(p.provider_name)?.slice(0, 1).toUpperCase() ??
+                    "—"
+                  }}</span>
                 </q-avatar>
               </div>
 
               <div class="col-10 row">
                 <div class="column col-9 justify-between">
-                  <span class="text-provider-close-nam font12 fontbold">{{ getFirstName(p.provider_name) || 'Prestador' }}</span>
-                  <span class="text-provider-close-region font9 fontbold text-grey-7">{{ p.district }}</span>
+                  <span class="text-provider-close-nam font12 fontbold">
+                    {{
+                      getFirstName(p.provider_name) || "Prestador"
+                    }}
+                  </span>
+
+                  <span
+                    v-if="getProviderGenderLabel(p, t)"
+                    class="font9 fontmedium text-grey-7"
+                  >
+                    {{ getProviderGenderLabel(p, t) }}
+                  </span>
+
+                  <span
+                    class="text-provider-close-region font9 fontbold text-grey-7"
+                   >
+                    {{ p.district }}
+                  </span>
+
                   <div class="row items-center justify-between q-pr-lg">
                     <div class="row items-center">
-                      <q-icon name="mdi-star" color="warning" size="12px" />
+                      <q-icon
+                        color="warning"
+                        name="mdi-star"
+                        size="12px"
+                      />
+
                       <span class="text-provider-close-rating font9 fontmedium">
-                        {{ p.average_rating != null ? (Number(p.average_rating).toFixed(1) + ' (' + (p.total_reviews ?? 0) + ')') : ('(' + (p.total_reviews ?? 0) + ')') }}
+                        {{
+                          p.average_rating != null
+                            ? Number(p.average_rating).toFixed(1) +
+                              " (" +
+                              (p.total_reviews ?? 0) +
+                              ")"
+                            : "(" + (p.total_reviews ?? 0) + ")"
+                        }}
                       </span>
                     </div>
+
                     <div class="row items-center">
-                      <q-icon name="mdi-broom" color="secondary" size="12px" />
-                      <span class="text-provider-close-jobs font9 fontmedium">{{ p.total_services ?? 0 }}</span>
+                      <q-icon
+                        color="secondary"
+                        name="mdi-broom"
+                        size="12px"
+                      />
+
+                      <span class="text-provider-close-jobs font9 fontmedium">
+                        {{ p.total_services ?? 0 }}
+                      </span>
                     </div>
+
                     <div class="row items-center">
-                      <q-icon name="mdi-map-marker-outline" color="text" size="12px" />
-                      <span class="text-provider-close-jobs font9 fontmedium">{{ (p.distance_km ?? '--') + ' km' }}</span>
+                      <q-icon
+                        color="text"
+                        name="mdi-map-marker-outline"
+                        size="12px"
+                      />
+
+                      <span class="text-provider-close-jobs font9 fontmedium">
+                        {{ (p.distance_km ?? "--") + " km" }}
+                      </span>
                     </div>
                   </div>
                 </div>
 
-                <div class="column col-3 justify-between text-center items-center">
-                  <span class="text-provider-close-price font12 fontbold">{{ priceByPeriod(p) }}</span>
+                <div
+                  class="column col-3 justify-between text-center items-center"
+                >
+                  <span class="text-provider-close-price font12 fontbold">
+                    {{ priceByPeriod(p) }}
+                  </span>
+
                   <div class="full-width">
                     <q-btn
-                      unelevated rounded no-caps
                       color="primary"
-                      size="sm"
+                      no-caps
                       padding="3px 12px"
+                      rounded
+                      size="sm"
+                      unelevated
                       :label="$t('search_page.schedule_btn')"
                       @click="goToScheduling(p)"
                     />
@@ -137,100 +262,131 @@
         </q-card>
       </div>
     </template>
-
   </q-page>
 </template>
 
 <script setup>
-import { ref, computed, onMounted } from 'vue';
-import { useRouter } from 'vue-router';
-import { useI18n } from 'vue-i18n';
-import { useQuasar } from 'quasar';
-import { buscaPrestadores } from 'src/api/dashboard';
-import { formatCurrency,getFirstName } from 'src/helpers/utils';
-import SearchFilterDialog from 'src/pages/search/components/SearchFilterDialog.vue';
-import SchedulingDialog from 'src/pages/search/components/SchedulingDialog.vue';
+import { avatarColors } from "src/helpers/avatarColors";
+import { buscaPrestadores } from "src/api/dashboard";
+import { computed, onMounted, ref } from "vue";
+import { formatCurrency, getFirstName } from "src/helpers/utils";
+import { getProviderGenderLabel } from "src/helpers/provider";
+import { useI18n } from "vue-i18n";
+import { useQuasar } from "quasar";
+import { useRouter } from "vue-router";
+
+import SchedulingDialog from "src/pages/search/components/SchedulingDialog.vue";
+import SearchFilterDialog from "src/pages/search/components/SearchFilterDialog.vue";
 
 const { t } = useI18n();
 const router = useRouter();
 const $q = useQuasar();
 
+const activeDate = ref(null);
+const activeSort = ref(null);
 const allProviders = ref([]);
-const loading      = ref(true);
-const searchName   = ref('');
-const activeDate   = ref(null);
-const activeSort   = ref(null);
-
 const currentPeriodType = ref(8);
-const periodTypeMap = { 2: 'daily_price_2h', 4: 'daily_price_4h', 6: 'daily_price_6h', 8: 'daily_price_8h' };
+const loading = ref(true);
+const searchName = ref("");
 
-const periodLabel = computed(() => {
-  const labels = { 8: t('search_page.until_8h'), 6: t('search_page.until_6h'), 4: t('search_page.until_4h'), 2: t('search_page.until_2h') };
-  return labels[currentPeriodType.value] ?? '';
-});
-
-const priceByPeriod = (p) => {
-  const key = periodTypeMap[currentPeriodType.value];
-  return p[key] ? formatCurrency(p[key]) : t('search_page.no_price');
+const periodTypeMap = {
+  2: "daily_price_2h",
+  4: "daily_price_4h",
+  6: "daily_price_6h",
+  8: "daily_price_8h",
 };
 
-// eslint-disable-next-line no-unused-vars
-const formatDistance = (distance) => {
-  if (distance === null || distance === undefined || distance === '') return '—';
-
-  const numericDistance = Number(distance);
-  if (!Number.isFinite(numericDistance)) return '—';
-
-  return `${numericDistance.toLocaleString('pt-BR', { maximumFractionDigits: 1 })} km`;
-};
-
-const setPeriodTypePrevious = () => {
-  const prev = currentPeriodType.value - 2;
-  if (periodTypeMap[prev]) currentPeriodType.value = prev;
-};
+const periodLabel = computed(() => {
+  const labels = {
+    8: t("search_page.until_8h"),
+    6: t("search_page.until_6h"),
+    4: t("search_page.until_4h"),
+    2: t("search_page.until_2h"),
+  };
 
-const setPeriodTypeNext = () => {
-  const next = currentPeriodType.value + 2;
-  if (periodTypeMap[next]) currentPeriodType.value = next;
-};
+  return labels[currentPeriodType.value] ?? "";
+});
 
 const sortedProviders = computed(() => {
   const list = [...allProviders.value];
+
   const priceKey = periodTypeMap[currentPeriodType.value];
 
   switch (activeSort.value) {
-    case 'price_asc':
-      return list.sort((a, b) => Number(a[priceKey] ?? 0) - Number(b[priceKey] ?? 0));
-    case 'price_desc':
-      return list.sort((a, b) => Number(b[priceKey] ?? 0) - Number(a[priceKey] ?? 0));
-    case 'rating_desc':
-      return list.sort((a, b) => Number(b.average_rating ?? 0) - Number(a.average_rating ?? 0));
-    case 'rating_asc':
-      return list.sort((a, b) => Number(a.average_rating ?? 0) - Number(b.average_rating ?? 0));
-    case 'reviews_desc':
-      return list.sort((a, b) => Number(b.total_reviews ?? 0) - Number(a.total_reviews ?? 0));
-    case 'reviews_asc':
-      return list.sort((a, b) => Number(a.total_reviews ?? 0) - Number(b.total_reviews ?? 0));
-    case 'services_desc':
-      return list.sort((a, b) => Number(b.total_services ?? 0) - Number(a.total_services ?? 0));
-    case 'services_asc':
-      return list.sort((a, b) => Number(a.total_services ?? 0) - Number(b.total_services ?? 0));
-    case 'oldest':
-      return list.sort((a, b) => new Date(a.created_at) - new Date(b.created_at));
-    case 'newest':
-      return list.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
+    case "price_asc":
+      return list.sort(
+        (a, b) => Number(a[priceKey] ?? 0) - Number(b[priceKey] ?? 0),
+      );
+    case "price_desc":
+      return list.sort(
+        (a, b) => Number(b[priceKey] ?? 0) - Number(a[priceKey] ?? 0),
+      );
+    case "rating_desc":
+      return list.sort(
+        (a, b) => Number(b.average_rating ?? 0) - Number(a.average_rating ?? 0),
+      );
+    case "rating_asc":
+      return list.sort(
+        (a, b) => Number(a.average_rating ?? 0) - Number(b.average_rating ?? 0),
+      );
+    case "reviews_desc":
+      return list.sort(
+        (a, b) => Number(b.total_reviews ?? 0) - Number(a.total_reviews ?? 0),
+      );
+    case "reviews_asc":
+      return list.sort(
+        (a, b) => Number(a.total_reviews ?? 0) - Number(b.total_reviews ?? 0),
+      );
+    case "services_desc":
+      return list.sort(
+        (a, b) => Number(b.total_services ?? 0) - Number(a.total_services ?? 0),
+      );
+    case "services_asc":
+      return list.sort(
+        (a, b) => Number(a.total_services ?? 0) - Number(b.total_services ?? 0),
+      );
+    case "oldest":
+      return list.sort(
+        (a, b) => new Date(a.created_at) - new Date(b.created_at),
+      );
+    case "newest":
+      return list.sort(
+        (a, b) => new Date(b.created_at) - new Date(a.created_at),
+      );
     default:
-      return list.sort((a, b) => Number(b.average_rating ?? 0) - Number(a.average_rating ?? 0));
+      return list.sort(
+        (a, b) => Number(b.average_rating ?? 0) - Number(a.average_rating ?? 0),
+      );
   }
 });
 
+// eslint-disable-next-line no-unused-vars
+const formatDistance = (distance) => {
+  if (distance === null || distance === undefined || distance === "")
+    return "—";
+
+  const numericDistance = Number(distance);
+  if (!Number.isFinite(numericDistance)) return "—";
+
+  return `${numericDistance.toLocaleString("pt-BR", { maximumFractionDigits: 1 })} km`;
+};
+
+const goToScheduling = (provider) => {
+  $q.dialog({
+    component: SchedulingDialog,
+    componentProps: { provider },
+  });
+};
+
 const loadProviders = async () => {
   loading.value = true;
+
   try {
-    allProviders.value = await buscaPrestadores({
-      name: searchName.value,
-      date: activeDate.value ?? '',
-    }) ?? [];
+    allProviders.value =
+      (await buscaPrestadores({
+        date: activeDate.value ?? "",
+        name: searchName.value,
+      })) ?? [];
   } catch {
     allProviders.value = [];
   } finally {
@@ -238,59 +394,74 @@ const loadProviders = async () => {
   }
 };
 
-const onNameChange = () => loadProviders();
+const onNameChange = () => {
+  loadProviders();
+};
 
 const openFilterDialog = () => {
   $q.dialog({
     component: SearchFilterDialog,
     componentProps: {
-      initialSort: activeSort.value,
       initialDate: activeDate.value,
+      initialSort: activeSort.value,
     },
-  }).onOk(({ sort, date }) => {
+  }).onOk(({ date, sort }) => {
     const dateChanged = date !== activeDate.value;
-    activeSort.value = sort;
+
     activeDate.value = date;
+    activeSort.value = sort;
+
     if (dateChanged) loadProviders();
   });
 };
 
-const goToScheduling = (provider) => {
-  $q.dialog({
-    component: SchedulingDialog,
-    componentProps: { provider },
-  });
+const priceByPeriod = (provider) => {
+  const key = periodTypeMap[currentPeriodType.value];
+
+  return provider[key]
+    ? formatCurrency(provider[key])
+    : t("search_page.no_price");
 };
 
-const avatarColors = [
-  { background: '#ffd5df', color: '#932e57' },
-  { background: '#d7e8ff', color: '#2158a8' },
-  { background: '#dfd',    color: '#2a7a3b' },
-  { background: '#ffe5cc', color: '#8a4500' },
-];
+const setPeriodTypeNext = () => {
+  const next = currentPeriodType.value + 2;
+
+  if (periodTypeMap[next]) currentPeriodType.value = next;
+};
+
+const setPeriodTypePrevious = () => {
+  const previous = currentPeriodType.value - 2;
 
-onMounted(() => loadProviders());
+  if (periodTypeMap[previous]) currentPeriodType.value = previous;
+};
+
+onMounted(loadProviders);
 </script>
 
 <style scoped lang="scss">
 .shadow-search {
   box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
 }
+
 .search-input {
   :deep(.q-field__control) {
     border-radius: 28px;
   }
 }
+
 .custom-schedule-card {
   border-radius: 12px;
 }
+
 .custom-schedule-btn {
   flex-shrink: 0;
   min-width: 72px;
 }
+
 .filter-active {
   color: var(--q-primary) !important;
 }
+
 .fonte-hint {
   line-height: 100%;
   letter-spacing: -0.04em;

+ 303 - 170
src/pages/search/components/OrderSummaryDialog.vue

@@ -1,33 +1,46 @@
 <template>
-  <q-dialog ref="dialogRef" maximized transition-show="slide-up" transition-hide="slide-down">
+  <q-dialog
+    ref="dialogRef"
+    maximized
+    transition-hide="slide-down"
+    transition-show="slide-up"
+  >
     <div class="dialog-root">
-
-      <div class="dialog-header row items-center q-px-md q-pt-md q-pb-sm bg-white">
-        <q-btn v-close-popup color="primary" dense flat icon="mdi-chevron-left" round />
+      <div
+        class="dialog-header row items-center q-px-md q-pt-md q-pb-sm bg-white"
+      >
+        <q-btn
+          v-close-popup
+          color="primary"
+          dense
+          flat
+          icon="mdi-chevron-left"
+          round
+        />
 
         <div class="col text-center font16 fontbold gradient-diarista">
-          {{ $t('scheduling_page.title') }}
+          {{ $t("scheduling_page.title") }}
         </div>
+
         <div style="width: 36px" />
       </div>
 
       <div class="dialog-body">
-
         <div class="q-px-md q-pt-md">
           <div class="info-banner card-border q-pa-md">
             <div class="font14 fontbold text-primary">
-              {{ $t('scheduling_page.order_summary.info_text') }}
+              {{ $t("scheduling_page.order_summary.info_text") }}
             </div>
 
             <div class="font12 fontmedium text-primary q-mt-xs">
-              {{ $t('scheduling_page.order_summary.info_note') }}
+              {{ $t("scheduling_page.order_summary.info_note") }}
             </div>
           </div>
         </div>
 
         <div class="q-px-md q-pt-md">
           <div class="font16 fontbold gradient-diarista q-mb-sm">
-            {{ $t('scheduling_page.order_summary.title') }}
+            {{ $t("scheduling_page.order_summary.title") }}
           </div>
 
           <q-card
@@ -40,11 +53,13 @@
               <div class="col text-center">
                 <div class="text-text">
                   <span class="font14 fontregular">
-                    {{ $t('scheduling_page.order_summary.service_label') }}
+                    {{ $t("scheduling_page.order_summary.service_label") }}
                   </span>
 
                   <span class="font14 fontbold">
-                    {{ ` ${booking.serviceType.label} (${booking.serviceType.hours})` }}
+                    {{
+                      ` ${booking.serviceType.label} (${booking.serviceType.hours})`
+                    }}
                   </span>
                 </div>
 
@@ -53,15 +68,22 @@
                 </div>
 
                 <div class="font14 fontbold text-text">
-                  {{ $t('scheduling_page.order_summary.time_range', { start: booking.slot.startHour, end: booking.slot.endHour }) }}
+                  {{
+                    $t("scheduling_page.order_summary.time_range", {
+                      start: booking.slot.startHour,
+                      end: booking.slot.endHour,
+                    })
+                  }}
                 </div>
               </div>
 
               <q-btn
                 color="grey-5"
-                flat round dense
+                dense
+                flat
                 icon="mdi-minus-circle-outline"
                 padding="4px 8px"
+                round
                 @click="confirmRemove(idx)"
               />
             </q-card-section>
@@ -69,8 +91,10 @@
             <q-btn
               class="full-width q-mt-sm"
               color="secondary"
+              no-caps
               padding="4px 8px"
-              unelevated rounded no-caps
+              rounded
+              unelevated
               :label="$t('scheduling_page.order_summary.send_btn')"
               @click="submitOrder"
             />
@@ -80,7 +104,9 @@
             <q-btn
               class="q-mt-xs"
               color="primary"
-              outline rounded no-caps
+              no-caps
+              outline
+              rounded
               :disable="showCalendar"
               :label="$t('scheduling_page.order_summary.add_date_btn')"
               @click="showCalendar = true"
@@ -88,12 +114,24 @@
           </div>
         </div>
 
-        <div v-if="showCalendar" class="q-px-md q-pt-lg q-pb-xl">
-          <div v-if="loadingAvailability" class="row items-center justify-center q-py-lg">
-            <q-spinner-dots color="primary" size="36px" />
+        <div
+          v-if="showCalendar"
+          class="q-px-md q-pt-lg q-pb-xl"
+        >
+          <div
+            v-if="loadingAvailability"
+            class="row items-center justify-center q-py-lg"
+          >
+            <q-spinner-dots
+              color="primary"
+              size="36px"
+            />
           </div>
 
-          <div v-else class="calendar-wrapper shadow-card">
+          <div
+            v-else
+            class="calendar-wrapper shadow-card"
+          >
             <q-date
               v-model="addDateValue"
               class=""
@@ -111,65 +149,131 @@
 </template>
 
 <script setup>
-import { computed, onMounted, ref } from 'vue';
-import { createSchedule, getClientProviderBlocks } from 'src/api/schedule';
-import { date } from 'quasar';
-import { getAddresses } from 'src/api/address';
-import { getProviderBlockedDays, getProviderWorkingDays } from 'src/api/providerAvailability';
-import { useDialogPluginComponent, useQuasar } from 'quasar';
-import { useI18n } from 'vue-i18n';
-import { userStore } from 'src/stores/user';
-import { useRouter } from 'vue-router';
-import { useScheduleCartStore } from 'src/stores/scheduleCart';
-
-import ServiceSelectionSheet from './ServiceSelectionSheet.vue';
-import ServiceTimeSelectionDialog from './ServiceTimeSelectionDialog.vue';
+import { computed, onMounted, ref } from "vue";
+import { createSchedule, getClientProviderBlocks } from "src/api/schedule";
+import { date, useDialogPluginComponent, useQuasar } from "quasar";
+import { getAddresses } from "src/api/address";
 
-const props = defineProps({
-  provider:       { type: Object, required: true },
-  initialBooking: { type: Object, required: true },
-});
+import {
+  getProviderBlockedDays,
+  getProviderWorkingDays,
+} from "src/api/providerAvailability";
+
+import { useI18n } from "vue-i18n";
+import { useRouter } from "vue-router";
+import { userStore } from "src/stores/user";
+import { useScheduleCartStore } from "src/stores/orders";
+
+import ServiceSelectionSheet from "./ServiceSelectionSheet.vue";
+import ServiceTimeSelectionDialog from "./ServiceTimeSelectionDialog.vue";
 
 defineEmits([...useDialogPluginComponent.emits]);
 
-const $q     = useQuasar();
-const router = useRouter();
+const props = defineProps({
+  initialBooking: { required: true, type: Object },
+  provider: { required: true, type: Object },
+});
 
+const { locale, t } = useI18n();
+const router = useRouter();
+const $q = useQuasar();
 const { dialogRef, onDialogOK } = useDialogPluginComponent();
 
-const { t, locale } = useI18n();
-
-const store        = userStore();
 const scheduleCart = useScheduleCartStore();
+const store = userStore();
 
-const bookings            = ref([props.initialBooking]);
-const submitting          = ref(false);
-const primaryAddress      = ref(null);
-const showCalendar        = ref(false);
-const addDateValue        = ref(null);
+const addDateValue = ref(null);
+const blockedDays = ref([]);
+const bookings = ref([props.initialBooking]);
 const loadingAvailability = ref(false);
-const workingDays         = ref([]);
-const blockedDays         = ref([]);
+const primaryAddress = ref(null);
 
-const providerClientBlocks = ref({ existing_schedules: [], fully_blocked_weeks: [] });
+const providerClientBlocks = ref({
+  existing_schedules: [],
+  fully_blocked_weeks: [],
+});
 
-const availableWeekDays = computed(() =>
-  [...new Set(workingDays.value.map(wd => wd.day))]
+const showCalendar = ref(false);
+const submitting = ref(false);
+const workingDays = ref([]);
+
+const availableWeekDays = computed(() => [
+  ...new Set(workingDays.value.map((wd) => wd.day)),
+]);
+
+const blockedDateSet = computed(
+  () =>
+    new Set(
+      blockedDays.value
+        .filter((bd) => bd.period === "all")
+        .map((bd) => bd.date),
+    ),
 );
 
-const blockedDateSet = computed(() =>
-  new Set(blockedDays.value.filter(bd => bd.period === 'all').map(bd => bd.date))
+const blockedWeekStartSet = computed(
+  () => new Set(providerClientBlocks.value.fully_blocked_weeks ?? []),
 );
 
-const blockedWeekStartSet = computed(() =>
-  new Set(providerClientBlocks.value.fully_blocked_weeks ?? [])
-);
+const normalizeDate = (d) => d.replace(/\//g, "-");
+
+const getWeekStart = (dateStr) => {
+  const d = new Date(normalizeDate(dateStr) + "T12:00:00");
+
+  d.setDate(d.getDate() - d.getDay());
+
+  return d.toISOString().slice(0, 10);
+};
+
+const getCartWeekCount = (newDateStr) => {
+  const newWeek = getWeekStart(newDateStr);
+
+  return scheduleCart.items.filter(
+    (item) =>
+      Number(item.provider_id) === Number(props.provider.provider_id) &&
+      getWeekStart(item.date) === newWeek,
+  ).length;
+};
+
+const getLocalWeekCount = (newDateStr) => {
+  const newWeek = getWeekStart(newDateStr);
+
+  return bookings.value.filter(
+    (booking) => getWeekStart(booking.date) === newWeek,
+  ).length;
+};
+
+const getServerWeekCount = (newDateStr) => {
+  const newWeek = getWeekStart(newDateStr);
+
+  return (providerClientBlocks.value.existing_schedules ?? []).filter(
+    (schedule) => getWeekStart(schedule.date) === newWeek,
+  ).length;
+};
+
+const wouldExceedWeekLimit = (newDateStr) => {
+  const count =
+    getServerWeekCount(newDateStr) +
+    getLocalWeekCount(newDateStr) +
+    getCartWeekCount(newDateStr);
+
+  return count >= 2;
+};
 
 const confirmRemove = (idx) => {
   $q.dialog({
-    title:      t('scheduling_page.order_summary.remove_confirm_title'),
-    cancel:     { label: t('scheduling_page.order_summary.remove_confirm_cancel'), flat: true, color: 'grey-6' },
-    ok:         { label: t('scheduling_page.order_summary.remove_confirm_ok'), unelevated: true, color: 'primary', rounded: true, noCaps: true },
+    title: t("scheduling_page.order_summary.remove_confirm_title"),
+    cancel: {
+      label: t("scheduling_page.order_summary.remove_confirm_cancel"),
+      flat: true,
+      color: "grey-6",
+    },
+    ok: {
+      label: t("scheduling_page.order_summary.remove_confirm_ok"),
+      unelevated: true,
+      color: "primary",
+      rounded: true,
+      noCaps: true,
+    },
     persistent: true,
   }).onOk(() => {
     bookings.value.splice(idx, 1);
@@ -177,68 +281,41 @@ const confirmRemove = (idx) => {
 };
 
 const dateOptions = (d) => {
-  const today = date.formatDate(new Date(), 'YYYY/MM/DD');
+  const today = date.formatDate(new Date(), "YYYY/MM/DD");
 
-  if (d < today)               return false;
+  if (d < today) return false;
   if (wouldExceedWeekLimit(d)) return false;
 
   const raw = normalizeDate(d);
 
   const parsed = new Date(`${raw}T12:00:00`);
 
-  const dayOfWeek     = parsed.getDay();
-  const isWorking     = availableWeekDays.value.includes(dayOfWeek);
-  const isBlocked     = blockedDateSet.value.has(raw);
+  const dayOfWeek = parsed.getDay();
+  const isWorking = availableWeekDays.value.includes(dayOfWeek);
+  const isBlocked = blockedDateSet.value.has(raw);
   const isWeekBlocked = blockedWeekStartSet.value.has(getWeekStart(raw));
 
   return isWorking && !isBlocked && !isWeekBlocked;
 };
 
 const formatDate = (dateStr) => {
-  const d = new Date(normalizeDate(dateStr) + 'T12:00:00');
+  const d = new Date(normalizeDate(dateStr) + "T12:00:00");
 
-  const localeMap = { pt: 'pt-BR', en: 'en-US', es: 'es-ES' };
+  const localeMap = { pt: "pt-BR", en: "en-US", es: "es-ES" };
 
-  const loc = localeMap[locale.value] ?? 'pt-BR';
+  const loc = localeMap[locale.value] ?? "pt-BR";
 
-  const weekday       = new Intl.DateTimeFormat(loc, { weekday: 'long' }).format(d);
-  const dateFormatted = new Intl.DateTimeFormat(loc, { day: '2-digit', month: '2-digit', year: 'numeric' }).format(d);
+  const weekday = new Intl.DateTimeFormat(loc, { weekday: "long" }).format(d);
+  const dateFormatted = new Intl.DateTimeFormat(loc, {
+    day: "2-digit",
+    month: "2-digit",
+    year: "numeric",
+  }).format(d);
 
   return `${weekday.charAt(0).toUpperCase() + weekday.slice(1)}, ${dateFormatted}`;
 };
 
-const formatHour = (h) => `${String(h).padStart(2, '0')}:00`;
-
-const getCartWeekCount = (newDateStr) => {
-  const newWeek = getWeekStart(newDateStr);
-
-  return scheduleCart.items.filter((item) =>
-    Number(item.provider_id) === Number(props.provider.provider_id) &&
-    getWeekStart(item.date) === newWeek
-  ).length;
-};
-
-const getLocalWeekCount = (newDateStr) => {
-  const newWeek = getWeekStart(newDateStr);
-
-  return bookings.value.filter((booking) => getWeekStart(booking.date) === newWeek).length;
-};
-
-const getServerWeekCount = (newDateStr) => {
-  const newWeek = getWeekStart(newDateStr);
-
-  return (providerClientBlocks.value.existing_schedules ?? []).filter(
-    (schedule) => getWeekStart(schedule.date) === newWeek
-  ).length;
-};
-
-const getWeekStart = (dateStr) => {
-  const d = new Date(normalizeDate(dateStr) + 'T12:00:00');
-
-  d.setDate(d.getDate() - d.getDay());
-
-  return d.toISOString().slice(0, 10);
-};
+const formatHour = (h) => `${String(h).padStart(2, "0")}:00`;
 
 const loadAvailability = async () => {
   loadingAvailability.value = true;
@@ -246,7 +323,10 @@ const loadAvailability = async () => {
   try {
     const clientId = store.user?.client_id;
 
-    const defaultClientBlocks = { existing_schedules: [], fully_blocked_weeks: [] };
+    const defaultClientBlocks = {
+      existing_schedules: [],
+      fully_blocked_weeks: [],
+    };
 
     const [wd, bd, clientBlocks] = await Promise.all([
       getProviderWorkingDays(props.provider.provider_id),
@@ -264,7 +344,10 @@ const loadAvailability = async () => {
     workingDays.value = [];
     blockedDays.value = [];
 
-    providerClientBlocks.value = { existing_schedules: [], fully_blocked_weeks: [] };
+    providerClientBlocks.value = {
+      existing_schedules: [],
+      fully_blocked_weeks: [],
+    };
   } finally {
     loadingAvailability.value = false;
   }
@@ -276,16 +359,14 @@ const loadPrimaryAddress = async () => {
 
     if (!clientId) return;
 
-    const addresses = await getAddresses('client', clientId);
+    const addresses = await getAddresses("client", clientId);
 
-    primaryAddress.value = (addresses ?? []).find(a => a.is_primary) ?? null;
+    primaryAddress.value = (addresses ?? []).find((a) => a.is_primary) ?? null;
   } catch {
     primaryAddress.value = null;
   }
 };
 
-const normalizeDate = (d) => d.replace(/\//g, '-');
-
 const onAddDateSelected = (val) => {
   if (!val) return;
 
@@ -294,47 +375,52 @@ const onAddDateSelected = (val) => {
   const valFormatted = normalizeDate(val);
 
   const blocksOfDate = blockedDays.value.filter(
-    bd => bd.date === valFormatted && bd.period !== 'all'
+    (bd) => bd.date === valFormatted && bd.period !== "all",
   );
 
   const dayOfWeek = new Date(`${valFormatted}T12:00:00`).getDay();
 
   const dayPeriods = workingDays.value
-    .filter(wd => wd.day === dayOfWeek)
-    .map(wd => wd.period);
+    .filter((wd) => wd.day === dayOfWeek)
+    .map((wd) => wd.period);
 
   const workingDayBlocks = [];
 
-  if (!dayPeriods.includes('afternoon')) {
-    workingDayBlocks.push({ init_hour: '14:00:00', end_hour: '20:00:00' });
+  if (!dayPeriods.includes("afternoon")) {
+    workingDayBlocks.push({ init_hour: "14:00:00", end_hour: "20:00:00" });
   }
 
-  if (!dayPeriods.includes('morning')) {
-    workingDayBlocks.push({ init_hour: '7:00:00', end_hour: '13:00:00' });
+  if (!dayPeriods.includes("morning")) {
+    workingDayBlocks.push({ init_hour: "7:00:00", end_hour: "13:00:00" });
   }
 
+  //
+
   const existingBookingBlocks = bookings.value
-    .filter(b => b.date.replace(/\//g, '-') === valFormatted)
-    .map(b => ({
+    .filter((b) => b.date.replace(/\//g, "-") === valFormatted)
+    .map((b) => ({
       init_hour: `${b.slot.startHour}:00:00`,
-      end_hour:  `${b.slot.endHour}:00:00`,
+      end_hour: `${b.slot.endHour}:00:00`,
     }));
 
-  const serverBookingBlocks = (providerClientBlocks.value.existing_schedules ?? [])
+  const serverBookingBlocks = (
+    providerClientBlocks.value.existing_schedules ?? []
+  )
     .filter((schedule) => schedule.date === valFormatted)
     .map((schedule) => ({
       init_hour: schedule.start_time,
-      end_hour:  schedule.end_time,
+      end_hour: schedule.end_time,
     }));
 
   const cartBookingBlocks = scheduleCart.items
-    .filter((item) =>
-      Number(item.provider_id) === Number(props.provider.provider_id) &&
-      normalizeDate(item.date) === valFormatted
+    .filter(
+      (item) =>
+        Number(item.provider_id) === Number(props.provider.provider_id) &&
+        normalizeDate(item.date) === valFormatted,
     )
     .map((item) => ({
       init_hour: item.start_time,
-      end_hour:  item.end_time,
+      end_hour: item.end_time,
     }));
 
   const partialBlocks = [
@@ -345,14 +431,30 @@ const onAddDateSelected = (val) => {
     ...cartBookingBlocks,
   ];
 
+  //
+
   $q.dialog({
-    component: ServiceSelectionSheet, componentProps: { provider: props.provider, selectedDate: val, partialBlocks },
+    component: ServiceSelectionSheet,
+    componentProps: {
+      provider: props.provider,
+      selectedDate: val,
+      partialBlocks,
+    },
   }).onOk(({ serviceType, date: date_, provider: prov }) => {
     $q.dialog({
-      component: ServiceTimeSelectionDialog, componentProps: { serviceType, selectedDate: date_, provider: prov, partialBlocks },
+      component: ServiceTimeSelectionDialog,
+      componentProps: {
+        serviceType,
+        selectedDate: date_,
+        provider: prov,
+        partialBlocks,
+      },
     }).onOk((booking) => {
       if (wouldExceedWeekLimit(booking.date)) {
-        $q.notify({ type: 'negative', message: t('scheduling_page.order_summary.week_limit_error') });
+        $q.notify({
+          type: "negative",
+          message: t("scheduling_page.order_summary.week_limit_error"),
+        });
 
         return;
       }
@@ -366,22 +468,26 @@ const onAddDateSelected = (val) => {
 
 const submitOrder = async () => {
   if (!primaryAddress.value) {
-    $q.notify({ type: 'warning', message: t('scheduling_page.order_summary.no_primary_address') });
+    $q.notify({
+      type: "warning",
+      message: t("scheduling_page.order_summary.no_primary_address"),
+    });
 
     return;
   }
 
-  const payloads = bookings.value.map(b => ({
-    client_id:     store.user.client_id,
-    provider_id:   props.provider.provider_id,
-    address_id:    primaryAddress.value.id,
-    schedule_type: 'default',
-    date:          normalizeDate(b.date),
-    period_type:   b.serviceType.hoursCount,
-    start_time:    formatHour(b.slot.startHour),
-    end_time:      formatHour(b.slot.endHour),
-    total_amount:  b.serviceType.basePrice ?? b.serviceType.price,
-    offers_meal:   b.meal === 'offer' ? true : b.meal === 'no_offer' ? false : null,
+  const payloads = bookings.value.map((b) => ({
+    client_id: store.user.client_id,
+    provider_id: props.provider.provider_id,
+    address_id: primaryAddress.value.id,
+    schedule_type: "default",
+    date: normalizeDate(b.date),
+    period_type: b.serviceType.hoursCount,
+    start_time: formatHour(b.slot.startHour),
+    end_time: formatHour(b.slot.endHour),
+    total_amount: b.serviceType.basePrice ?? b.serviceType.price,
+    offers_meal:
+      b.meal === "offer" ? true : b.meal === "no_offer" ? false : null,
   }));
 
   submitting.value = true;
@@ -389,28 +495,26 @@ const submitOrder = async () => {
   try {
     await Promise.all(payloads.map((payload) => createSchedule(payload)));
 
-    $q.notify({ type: 'positive', message: t('scheduling_page.order_summary.submit_success') });
+    $q.notify({
+      type: "positive",
+      message: t("scheduling_page.order_summary.submit_success"),
+    });
 
     onDialogOK();
   } catch (err) {
-    const msg = err?.response?.data?.message
-      ?? err?.message
-      ?? t('scheduling_page.order_summary.submit_error');
+    const msg =
+      err?.response?.data?.message ??
+      err?.message ??
+      t("scheduling_page.order_summary.submit_error");
 
-    $q.notify({ type: 'negative', message: msg });
+    $q.notify({ type: "negative", message: msg });
   } finally {
     submitting.value = false;
 
-    router.push({ name: 'DashboardPage' });
+    router.push({ name: "DashboardPage" });
   }
 };
 
-const wouldExceedWeekLimit = (newDateStr) => {
-  const count = getServerWeekCount(newDateStr) + getLocalWeekCount(newDateStr) + getCartWeekCount(newDateStr);
-
-  return count >= 2;
-};
-
 onMounted(() => Promise.all([loadAvailability(), loadPrimaryAddress()]));
 </script>
 
@@ -423,7 +527,7 @@ onMounted(() => Promise.all([loadAvailability(), loadPrimaryAddress()]));
   display: flex;
   flex-direction: column;
   overflow: hidden;
-  background: #F9FAFB;
+  background: #f9fafb;
 }
 
 .dialog-header {
@@ -448,30 +552,59 @@ onMounted(() => Promise.all([loadAvailability(), loadPrimaryAddress()]));
   overflow: hidden;
   background: white;
 
-  :deep(.q-date) { background: white; width: 100%; }
+  :deep(.q-date) {
+    background: white;
+    width: 100%;
+  }
   :deep(.q-date__main),
   :deep(.q-date__content),
-  :deep(.q-date__calendar) { background: white !important; }
+  :deep(.q-date__calendar) {
+    background: white !important;
+  }
 
   :deep(.q-date__calendar-item .q-btn) {
     min-width: 0 !important;
     padding: 6px 2px !important;
   }
   :deep(.q-date__calendar-item .q-btn.disabled),
-  :deep(.q-date__calendar-item .q-btn[disabled]) { opacity: 1 !important; }
+  :deep(.q-date__calendar-item .q-btn[disabled]) {
+    opacity: 1 !important;
+  }
   :deep(.q-date__calendar-item .q-btn.disabled .q-btn__content),
-  :deep(.q-date__calendar-item .q-btn[disabled] .q-btn__content) { color: #CBD5E1 !important; }
+  :deep(.q-date__calendar-item .q-btn[disabled] .q-btn__content) {
+    color: #cbd5e1 !important;
+  }
 
-  :deep(.q-date__calendar-days .q-btn__content) { font-weight: 500; color: #1E293B; }
-  :deep(.q-date__calendar-weekdays > div) { color: #6366F1; font-weight: 700; opacity: 0.8; }
+  :deep(.q-date__calendar-days .q-btn__content) {
+    font-weight: 500;
+    color: #1e293b;
+  }
+  :deep(.q-date__calendar-weekdays > div) {
+    color: #6366f1;
+    font-weight: 700;
+    opacity: 0.8;
+  }
   :deep(.q-date__navigation) {
-    .q-btn { color: #6366F1 !important; }
-    .q-btn__content { color: #6366F1 !important; }
+    .q-btn {
+      color: #6366f1 !important;
+    }
+    .q-btn__content {
+      color: #6366f1 !important;
+    }
+  }
+  :deep(.q-date__event) {
+    bottom: 4px;
+    height: 6px;
+    width: 6px;
+    border-radius: 50%;
+  }
+  :deep(.q-date__today .q-btn__content) {
+    color: #7c4dff !important;
+    background: #7c4dff15;
+    border-radius: 50%;
   }
-  :deep(.q-date__event) { bottom: 4px; height: 6px; width: 6px; border-radius: 50%; }
-  :deep(.q-date__today .q-btn__content) { color: #7c4dff !important; background: #7c4dff15; border-radius: 50%; }
   :deep(.q-date__selected .q-btn__content) {
-    background: #6366F1 !important;
+    background: #6366f1 !important;
     color: white !important;
     border-radius: 50%;
     box-shadow: 0 4px 10px rgba(99, 102, 241, 0.4);
@@ -496,33 +629,33 @@ onMounted(() => Promise.all([loadAvailability(), loadPrimaryAddress()]));
 
   :deep(.q-date__calendar-item--out) {
     .q-btn__content {
-      color: #CBD5E1 !important;
+      color: #cbd5e1 !important;
     }
   }
 
   :deep(.q-date__calendar-days .q-btn__content) {
     font-weight: 500;
-    color: #1E293B;
+    color: #1e293b;
   }
 
   :deep(.q-date__calendar-weekdays > div) {
-    color: #6366F1;
+    color: #6366f1;
     font-weight: 700;
     opacity: 0.8;
   }
 
   :deep(.q-date__navigation) {
     .q-btn {
-      color: #1E293B !important;
+      color: #1e293b !important;
     }
     .q-btn__content {
-      color: #1E293B !important;
+      color: #1e293b !important;
     }
   }
 
   :deep(.q-date__nav-btn-month),
   :deep(.q-date__nav-btn-year) {
-    color: #6366F1 !important;
+    color: #6366f1 !important;
     font-weight: 700;
   }
 
@@ -543,7 +676,7 @@ onMounted(() => Promise.all([loadAvailability(), loadPrimaryAddress()]));
 
   :deep(.q-date__selected) {
     .q-btn__content {
-      background: #6366F1 !important;
+      background: #6366f1 !important;
       color: white !important;
       border-radius: 50%;
       box-shadow: 0 4px 10px rgba(99, 102, 241, 0.4);
@@ -553,7 +686,7 @@ onMounted(() => Promise.all([loadAvailability(), loadPrimaryAddress()]));
   :deep(.q-date__view--months),
   :deep(.q-date__view--years) {
     .q-btn {
-      color: #1E293B !important;
+      color: #1e293b !important;
     }
   }
 }

+ 315 - 199
src/pages/search/components/SchedulingDialog.vue

@@ -6,7 +6,9 @@
     transition-show="slide-up"
   >
     <div class="dialog-root">
-      <div class="dialog-header row items-center q-px-md q-pt-md q-pb-sm bg-white">
+      <div
+        class="dialog-header row items-center q-px-md q-pt-md q-pb-sm bg-white"
+      >
         <q-btn
           v-close-popup
           color="primary"
@@ -16,8 +18,10 @@
           round
         />
 
-        <div class="col text-center font16 fontbold text-primary gradient-diarista q-mb-xs">
-          {{ $t('scheduling_page.title') }}
+        <div
+          class="col text-center font16 fontbold text-primary gradient-diarista q-mb-xs"
+        >
+          {{ $t("scheduling_page.title") }}
         </div>
 
         <div style="width: 36px"></div>
@@ -26,48 +30,74 @@
       <div class="dialog-body">
         <div class="q-px-md q-pt-md">
           <div class="font16 fontbold gradient-diarista q-mb-xs">
-            {{ $t('scheduling_page.about_provider') }}
+            {{ $t("scheduling_page.about_provider") }}
           </div>
 
-          <q-card :flat="false" class="card-border shadow-card bg-surface text-text">
+          <q-card
+            class="card-border shadow-card bg-surface text-text"
+            :flat="false"
+          >
             <q-card-section class="q-pa-md">
               <div class="row items-center no-wrap q-gutter-x-md">
                 <div class="col-2">
-                  <q-avatar :style="avatarColors[provider.provider_id % avatarColors.length]">
+                  <q-avatar
+                    :style="
+                      avatarColors[provider.provider_id % avatarColors.length]
+                    "
+                  >
                     <img
-                      v-if="provider.provider_photo"
-                      :src="provider.provider_photo"
-                      style="object-fit:cover;border-radius:50%;"
+                      v-if="getProfileMediaUrl(provider)"
+                      style="object-fit: cover; border-radius: 50%"
+                      :src="getProfileMediaUrl(provider)"
                     />
 
                     <span v-else>
-                      {{ getFirstName(provider.provider_name)?.slice(0, 2).toUpperCase() ?? '??' }}
+                      {{
+                        getFirstName(provider.provider_name)
+                          ?.slice(0, 2)
+                          .toUpperCase() ?? "??"
+                      }}
                     </span>
                   </q-avatar>
                 </div>
 
                 <div class="col-3 column">
                   <div class="font12 fontbold text-text">
-                   {{ getFirstName(provider?.provider_name) ?? '—' }}
+                    {{ getFirstName(provider?.provider_name) ?? "—" }}
                   </div>
 
                   <div class="font10 fontmedium text-grey-7">
-                    {{ provider?.district ?? '—' }}
+                    {{ provider?.district ?? "—" }}
                   </div>
                 </div>
 
                 <div class="col-3">
                   <div class="row items-center q-gutter-x-md q-mt-xs">
                     <div class="row items-center">
-                      <q-icon color="warning" name="mdi-star" size="12px" />
+                      <q-icon
+                        color="warning"
+                        name="mdi-star"
+                        size="12px"
+                      />
 
                       <span class="font9 fontmedium q-ml-xs">
-                        {{ (provider?.average_rating != null ? Number(provider.average_rating).toFixed(1) : '') + ' (' + (provider?.total_reviews ?? 0) + ')' }}
+                        {{
+                          (provider?.average_rating != null
+                            ? Number(provider.average_rating).toFixed(1)
+                            : "") +
+                          " (" +
+                          (provider?.total_reviews ?? 0) +
+                          ")"
+                        }}
                       </span>
                     </div>
 
                     <div class="row items-center">
-                      <q-icon color="secondary" name="mdi-broom" size="14px" />
+                      <q-icon
+                        color="secondary"
+                        name="mdi-broom"
+                        size="14px"
+                      />
 
                       <span class="font9 fontmedium q-ml-xs">
                         {{ provider?.total_services ?? 0 }}
@@ -81,10 +111,10 @@
                     color="pink-4"
                     dense
                     flat
-                    :icon="isFavorite ? 'mdi-heart' : 'mdi-heart-outline'"
-                    :loading="favoriteLoading"
                     round
                     size="sm"
+                    :icon="isFavorite ? 'mdi-heart' : 'mdi-heart-outline'"
+                    :loading="favoriteLoading"
                     @click="toggleFavorite"
                   />
 
@@ -104,14 +134,23 @@
 
         <div class="q-px-md q-pt-lg">
           <div class="font16 fontbold gradient-diarista q-mb-xs">
-            {{ $t('scheduling_page.schedule_service') }}
+            {{ $t("scheduling_page.schedule_service") }}
           </div>
 
-          <div v-if="loadingAvailability" class="row items-center justify-center q-py-lg">
-            <q-spinner-dots color="primary" size="36px" />
+          <div
+            v-if="loadingAvailability"
+            class="row items-center justify-center q-py-lg"
+          >
+            <q-spinner-dots
+              color="primary"
+              size="36px"
+            />
           </div>
 
-          <div v-else class="calendar-wrapper shadow-card q-mb-md">
+          <div
+            v-else
+            class="calendar-wrapper shadow-card q-mb-md"
+          >
             <q-date
               v-model="selectedDate"
               class="full-width"
@@ -127,11 +166,11 @@
         <div class="q-px-md q-pt-sm q-pb-xl">
           <div class="row items-center justify-between q-mb-sm">
             <div class="font16 fontbold gradient-diarista">
-              {{ $t('scheduling_page.reviews_title') }}
+              {{ $t("scheduling_page.reviews_title") }}
             </div>
 
-            <span class=" text-text cursor-pointer">
-              {{ $t('scheduling_page.see_all') }}
+            <span class="text-text cursor-pointer">
+              {{ $t("scheduling_page.see_all") }}
 
               <q-icon
                 class="text-text"
@@ -140,18 +179,27 @@
             </span>
           </div>
 
-          <div v-if="loadingReviews" class="row items-center justify-center q-py-md">
+          <div
+            v-if="loadingReviews"
+            class="row items-center justify-center q-py-md"
+          >
             <q-spinner-dots
               color="primary"
               size="36px"
             />
           </div>
 
-          <div v-else-if="reviews.length === 0" class="text-center text-grey-6 text-body2 q-py-md">
-            {{ $t('scheduling_page.no_reviews') }}
+          <div
+            v-else-if="reviews.length === 0"
+            class="text-center text-grey-6 text-body2 q-py-md"
+          >
+            {{ $t("scheduling_page.no_reviews") }}
           </div>
 
-          <div v-else class="reviews-scroll">
+          <div
+            v-else
+            class="reviews-scroll"
+          >
             <q-card
               v-for="review in reviews"
               :key="review.id"
@@ -160,20 +208,29 @@
             >
               <q-card-section class="q-pa-sm">
                 <div class="row items-center no-wrap q-gutter-x-sm q-mb-xs">
-                  <q-avatar :style="avatarColors[review.schedule?.client?.id % avatarColors.length]">
+                  <q-avatar
+                    :style="
+                      avatarColors[
+                        review.schedule?.client?.id % avatarColors.length
+                      ]
+                    "
+                  >
                     <img
                       v-if="review.schedule?.client?.profile_photo"
+                      style="object-fit: cover; border-radius: 50%"
                       :src="review.schedule?.client?.profile_photo"
-                      style="object-fit:cover;border-radius:50%;"
                     />
 
                     <span v-else>
-                      {{review.schedule?.client?.name?.slice(0,1) ?? '—' }}
+                      {{ review.schedule?.client?.name?.slice(0, 1) ?? "—" }}
                     </span>
                   </q-avatar>
 
                   <div class="col-3 text-text font10 fontbold">
-                    {{ review.schedule?.client?.name ?? $t('scheduling_page.unknown_client') }}
+                    {{
+                      review.schedule?.client?.name ??
+                      $t("scheduling_page.unknown_client")
+                    }}
                   </div>
 
                   <div class="row items-center q-mb-xs q-my-auto">
@@ -188,7 +245,7 @@
                 </div>
 
                 <div class="font9 fontregular text-text review-comment">
-                  {{ review.comment ?? '--------' }}
+                  {{ review.comment ?? "--------" }}
                 </div>
               </q-card-section>
             </q-card>
@@ -200,139 +257,157 @@
 </template>
 
 <script setup>
-import { computed, onMounted, ref } from 'vue';
-import { date } from 'quasar';
-import { getClientProviderBlocks } from 'src/api/schedule';
-import { getProviderBlockedDays, getProviderWorkingDays } from 'src/api/providerAvailability';
-import { getProviderReceivedReviews } from 'src/api/review';
+import { avatarColors } from "src/helpers/avatarColors";
+import { computed, onMounted, ref } from "vue";
+
 import {
   createClientFavoriteProvider,
   deleteClientFavoriteProvider,
   getClientFavoriteProviders,
-} from 'src/api/clientFavoriteProvider';
-import { useDialogPluginComponent, useQuasar } from 'quasar';
-import { useI18n } from 'vue-i18n';
-import { useScheduleCartStore } from 'src/stores/scheduleCart';
-import { userStore } from 'src/stores/user';
-import {getFirstName} from 'src/helpers/utils';
-import OrderSummaryDialog from './OrderSummaryDialog.vue';
-import ServiceSelectionSheet from './ServiceSelectionSheet.vue';
-import ServiceTimeSelectionDialog from './ServiceTimeSelectionDialog.vue';
+} from "src/api/clientFavoriteProvider";
 
-const props = defineProps({
-  provider: { required: true, type: Object },
-});
+import { date, useDialogPluginComponent, useQuasar } from "quasar";
+import { getClientProviderBlocks } from "src/api/schedule";
+import { getFirstName } from "src/helpers/utils";
+import { getProfileMediaUrl } from "src/helpers/profileMedia";
+
+import {
+  getProviderBlockedDays,
+  getProviderWorkingDays,
+} from "src/api/providerAvailability";
+
+import { getProviderReceivedReviews } from "src/api/review";
+import { useI18n } from "vue-i18n";
+import { userStore } from "src/stores/user";
+import { useScheduleCartStore } from "src/stores/orders";
+
+import OrderSummaryDialog from "./OrderSummaryDialog.vue";
+import ServiceSelectionSheet from "./ServiceSelectionSheet.vue";
+import ServiceTimeSelectionDialog from "./ServiceTimeSelectionDialog.vue";
 
 defineEmits([...useDialogPluginComponent.emits]);
 
-const { dialogRef } = useDialogPluginComponent();
+const props = defineProps({
+  provider: { required: true, type: Object },
+});
 
-const $q    = useQuasar();
 const { t } = useI18n();
-
-const store = userStore();
+const $q = useQuasar();
+const { dialogRef } = useDialogPluginComponent();
 
 const scheduleCart = useScheduleCartStore();
+const store = userStore();
 
-const selectedDate        = ref(null);
-const workingDays         = ref([]);
-const blockedDays         = ref([]);
-const loadingAvailability = ref(true);
-const reviews             = ref([]);
-const loadingReviews      = ref(true);
-const isFavorite          = ref(false);
-const favoriteId          = ref(null);
-const favoriteLoading     = ref(false);
-
-const avatarColors = [
-  { background: '#ffd5df', color: '#932e57' },
-  { background: '#d7e8ff', color: '#2158a8' },
-  { background: '#dfd',    color: '#2a7a3b' },
-  { background: '#ffe5cc', color: '#8a4500' },
-];
-
+const blockedDays = ref([]);
 const bookings = ref([]);
-
-const providerClientBlocks = ref({ existing_schedules: [], fully_blocked_weeks: [] });
-
-const availableWeekDays = computed(() =>
-  [...new Set(workingDays.value.map((wd) => wd.day))]
-);
-
-const blockedDateSet = computed(() =>
-  new Set(
-    blockedDays.value
-      .filter((bd) => bd.period === 'all')
-      .map((bd) => bd.date)
-  )
+const favoriteId = ref(null);
+const favoriteLoading = ref(false);
+const isFavorite = ref(false);
+const loadingAvailability = ref(true);
+const loadingReviews = ref(true);
+const providerClientBlocks = ref({
+  existing_schedules: [],
+  fully_blocked_weeks: [],
+});
+const reviews = ref([]);
+const selectedDate = ref(null);
+const workingDays = ref([]);
+
+const availableWeekDays = computed(() => [
+  ...new Set(workingDays.value.map((wd) => wd.day)),
+]);
+
+const blockedDateSet = computed(
+  () =>
+    new Set(
+      blockedDays.value
+        .filter((bd) => bd.period === "all")
+        .map((bd) => bd.date),
+    ),
 );
 
-const blockedWeekStartSet = computed(() =>
-  new Set(providerClientBlocks.value.fully_blocked_weeks ?? [])
+const blockedWeekStartSet = computed(
+  () => new Set(providerClientBlocks.value.fully_blocked_weeks ?? []),
 );
 
-const dateOptions = (d) => {
-  const today = date.formatDate(new Date(), 'YYYY/MM/DD');
-
-  if (d < today) return false;
-
-  const raw       = normalizeDate(d);
-  const parsed    = new Date(`${raw}T12:00:00`);
-  const dayOfWeek = parsed.getDay();
+const normalizeDate = (dateStr) => (dateStr ?? "").replace(/\//g, "-");
 
-  const isWorkingDay  = availableWeekDays.value.includes(dayOfWeek);
-  const isBlocked     = blockedDateSet.value.has(raw);
-  const isWeekBlocked = blockedWeekStartSet.value.has(getWeekStart(raw));
+const getWeekStart = (dateStr) => {
+  const normalizedDate = normalizeDate(dateStr);
 
-  if (!isWorkingDay || isBlocked || isWeekBlocked) return false;
+  const d = new Date(`${normalizedDate}T12:00:00`);
 
-  if (wouldExceedWeekLimit(raw)) return false;
+  d.setDate(d.getDate() - d.getDay());
 
-  return true;
+  return d.toISOString().split("T")[0];
 };
 
-const formatHour = (h) => `${String(h).padStart(2, '0')}:00`;
-
 const getCartWeekCount = (selectedDate) => {
   const weekStart = getWeekStart(selectedDate);
 
-  return scheduleCart.items.filter((item) =>
-    Number(item.provider_id) === Number(props.provider.provider_id) &&
-    getWeekStart(item.date) === weekStart
+  return scheduleCart.items.filter(
+    (item) =>
+      Number(item.provider_id) === Number(props.provider.provider_id) &&
+      getWeekStart(item.date) === weekStart,
   ).length;
 };
 
 const getLocalWeekCount = (selectedDate) => {
   const weekStart = getWeekStart(selectedDate);
 
-  return bookings.value.filter((booking) => getWeekStart(booking.date) === weekStart).length;
+  return bookings.value.filter(
+    (booking) => getWeekStart(booking.date) === weekStart,
+  ).length;
 };
 
 const getServerWeekCount = (selectedDate) => {
   const weekStart = getWeekStart(selectedDate);
 
   return (providerClientBlocks.value.existing_schedules ?? []).filter(
-    (schedule) => getWeekStart(schedule.date) === weekStart
+    (schedule) => getWeekStart(schedule.date) === weekStart,
   ).length;
 };
 
-const getWeekStart = (dateStr) => {
-  const normalizedDate = normalizeDate(dateStr);
+const wouldExceedWeekLimit = (selectedDate) => {
+  const cartCount = getCartWeekCount(selectedDate);
+  const localCount = getLocalWeekCount(selectedDate);
+  const serverCount = getServerWeekCount(selectedDate);
 
-  const d = new Date(`${normalizedDate}T12:00:00`);
+  return serverCount + localCount + cartCount >= 2;
+};
 
-  d.setDate(d.getDate() - d.getDay());
+const dateOptions = (d) => {
+  const today = date.formatDate(new Date(), "YYYY/MM/DD");
+
+  if (d < today) return false;
+
+  const raw = normalizeDate(d);
+  const parsed = new Date(`${raw}T12:00:00`);
+  const dayOfWeek = parsed.getDay();
 
-  return d.toISOString().split('T')[0];
+  const isWorkingDay = availableWeekDays.value.includes(dayOfWeek);
+  const isBlocked = blockedDateSet.value.has(raw);
+  const isWeekBlocked = blockedWeekStartSet.value.has(getWeekStart(raw));
+
+  if (!isWorkingDay || isBlocked || isWeekBlocked) return false;
+
+  if (wouldExceedWeekLimit(raw)) return false;
+
+  return true;
 };
 
+const formatHour = (h) => `${String(h).padStart(2, "0")}:00`;
+
 const loadAvailability = async () => {
   loadingAvailability.value = true;
 
   try {
     const clientId = store.user?.client?.id;
 
-    const defaultClientBlocks = { existing_schedules: [], fully_blocked_weeks: [] };
+    const defaultClientBlocks = {
+      existing_schedules: [],
+      fully_blocked_weeks: [],
+    };
 
     const [wd, bd, clientBlocks] = await Promise.all([
       getProviderWorkingDays(props.provider.provider_id),
@@ -350,7 +425,10 @@ const loadAvailability = async () => {
   } catch {
     blockedDays.value = [];
 
-    providerClientBlocks.value = { existing_schedules: [], fully_blocked_weeks: [] };
+    providerClientBlocks.value = {
+      existing_schedules: [],
+      fully_blocked_weeks: [],
+    };
 
     workingDays.value = [];
   } finally {
@@ -358,20 +436,6 @@ const loadAvailability = async () => {
   }
 };
 
-const loadReviews = async () => {
-  loadingReviews.value = true;
-
-  try {
-    const all = await getProviderReceivedReviews(props.provider.provider_id);
-
-    reviews.value = (all ?? []).slice(0, 10);
-  } catch {
-    reviews.value = [];
-  } finally {
-    loadingReviews.value = false;
-  }
-};
-
 const loadFavoriteState = async () => {
   const clientId = store.user?.client?.id;
   if (!clientId) return;
@@ -379,7 +443,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(props.provider.provider_id),
     );
 
     isFavorite.value = !!match;
@@ -390,43 +454,20 @@ const loadFavoriteState = async () => {
   }
 };
 
-const toggleFavorite = async () => {
-  if (favoriteLoading.value) return;
-
-  const clientId = store.user?.client?.id;
-  if (!clientId) {
-    $q.notify({ type: 'negative', message: t('scheduling_page.favorite_error') });
-    return;
-  }
-
-  favoriteLoading.value = true;
+const loadReviews = async () => {
+  loadingReviews.value = true;
 
   try {
-    if (isFavorite.value) {
-      if (favoriteId.value) {
-        await deleteClientFavoriteProvider(favoriteId.value);
-      }
-      isFavorite.value = false;
-      favoriteId.value = null;
-      $q.notify({ type: 'positive', message: t('scheduling_page.favorite_removed') });
-    } else {
-      const created = await createClientFavoriteProvider({
-        client_id: clientId,
-        provider_id: props.provider.provider_id,
-      });
-      isFavorite.value = true;
-      favoriteId.value = created?.id ?? null;
-      $q.notify({ type: 'positive', message: t('scheduling_page.favorite_added') });
-    }
+    const all = await getProviderReceivedReviews(props.provider.provider_id);
+
+    reviews.value = (all ?? []).slice(0, 10);
   } catch {
-    $q.notify({ type: 'negative', message: t('scheduling_page.favorite_error') });
+    reviews.value = [];
   } finally {
-    favoriteLoading.value = false;
+    loadingReviews.value = false;
   }
 };
 
-const normalizeDate = (dateStr) => (dateStr ?? '').replace(/\//g, '-');
-
 const onDateSelected = (val) => {
   if (!val) return;
 
@@ -435,7 +476,7 @@ const onDateSelected = (val) => {
   const valFormatted = normalizeDate(val);
 
   const blocksOfDate = blockedDays.value.filter(
-    (bd) => bd.date === valFormatted && bd.period !== 'all'
+    (bd) => bd.date === valFormatted && bd.period !== "all",
   );
 
   const dayOfWeek = new Date(`${valFormatted}T12:00:00`).getDay();
@@ -446,35 +487,38 @@ const onDateSelected = (val) => {
 
   const workingDayBlocks = [];
 
-  if (!dayPeriods.includes('afternoon')) {
-    workingDayBlocks.push({ init_hour: '14:00:00', end_hour: '20:00:00' });
+  if (!dayPeriods.includes("afternoon")) {
+    workingDayBlocks.push({ init_hour: "14:00:00", end_hour: "20:00:00" });
   }
 
-  if (!dayPeriods.includes('morning')) {
-    workingDayBlocks.push({ init_hour: '07:00:00', end_hour: '13:00:00' });
+  if (!dayPeriods.includes("morning")) {
+    workingDayBlocks.push({ init_hour: "07:00:00", end_hour: "13:00:00" });
   }
 
   const localBookingBlocks = bookings.value
-    .filter((booking) => booking.date.replace(/\//g, '-') === valFormatted)
+    .filter((booking) => booking.date.replace(/\//g, "-") === valFormatted)
     .map((booking) => ({
-      end_hour:  `${booking.slot.endHour}:00:00`,
+      end_hour: `${booking.slot.endHour}:00:00`,
       init_hour: `${booking.slot.startHour}:00:00`,
     }));
 
   const cartBookingBlocks = scheduleCart.items
-    .filter((item) =>
-      Number(item.provider_id) === Number(props.provider.provider_id) &&
-      normalizeDate(item.date) === valFormatted
+    .filter(
+      (item) =>
+        Number(item.provider_id) === Number(props.provider.provider_id) &&
+        normalizeDate(item.date) === valFormatted,
     )
     .map((item) => ({
-      end_hour:  item.end_time,
+      end_hour: item.end_time,
       init_hour: item.start_time,
     }));
 
-  const serverBookingBlocks = (providerClientBlocks.value.existing_schedules ?? [])
+  const serverBookingBlocks = (
+    providerClientBlocks.value.existing_schedules ?? []
+  )
     .filter((schedule) => normalizeDate(schedule.date) === valFormatted)
     .map((schedule) => ({
-      end_hour:  schedule.end_time,
+      end_hour: schedule.end_time,
       init_hour: schedule.start_time,
     }));
 
@@ -487,25 +531,46 @@ const onDateSelected = (val) => {
   ];
 
   $q.dialog({
-    component: ServiceSelectionSheet, componentProps: { partialBlocks, provider: props.provider, selectedDate: val },
+    component: ServiceSelectionSheet,
+    componentProps: {
+      partialBlocks,
+      provider: props.provider,
+      selectedDate: val,
+    },
   }).onOk(({ action, serviceType, date: date_, provider: prov }) => {
     $q.dialog({
-      component: ServiceTimeSelectionDialog, componentProps: {partialBlocks, provider: prov, selectedDate: date_, serviceType },
+      component: ServiceTimeSelectionDialog,
+      componentProps: {
+        partialBlocks,
+        provider: prov,
+        selectedDate: date_,
+        serviceType,
+      },
     }).onOk((booking) => {
-      if (action === 'cart') {
+      if (action === "cart") {
         scheduleCart.addItem({
-          date:          normalizeDate(booking.date),
-          end_time:      formatHour(booking.slot.endHour),
-          local_id:      `${prov.provider_id}-${booking.date}-${booking.slot.value}-${Date.now()}`,
-          offers_meal:   booking.meal === 'offer' ? true : booking.meal === 'no_offer' ? false : null,
-          period_type:   booking.serviceType.hoursCount,
-          provider_id:   prov.provider_id,
+          date: normalizeDate(booking.date),
+          end_time: formatHour(booking.slot.endHour),
+          local_id: `${prov.provider_id}-${booking.date}-${booking.slot.value}-${Date.now()}`,
+          offers_meal:
+            booking.meal === "offer"
+              ? true
+              : booking.meal === "no_offer"
+                ? false
+                : null,
+          period_type: booking.serviceType.hoursCount,
+          provider_id: prov.provider_id,
           provider_name: prov.provider_name,
-          start_time:    formatHour(booking.slot.startHour),
-          base_amount:   booking.serviceType.basePrice ?? booking.serviceType.price,
+          provider_photo: getProfileMediaUrl(prov),
+          start_time: formatHour(booking.slot.startHour),
+          base_amount:
+            booking.serviceType.basePrice ?? booking.serviceType.price,
         });
 
-        $q.notify({ message: t('schedule_cart.add_success'), type: 'positive' });
+        $q.notify({
+          message: t("schedule_cart.add_success"),
+          type: "positive",
+        });
 
         return;
       }
@@ -513,7 +578,8 @@ const onDateSelected = (val) => {
       bookings.value.push(booking);
 
       $q.dialog({
-        component: OrderSummaryDialog, componentProps: { initialBooking: booking, provider: props.provider },
+        component: OrderSummaryDialog,
+        componentProps: { initialBooking: booking, provider: props.provider },
       }).onOk(() => {
         dialogRef.value.hide();
       });
@@ -521,18 +587,62 @@ const onDateSelected = (val) => {
   });
 };
 
-const wouldExceedWeekLimit = (selectedDate) => {
-  const cartCount   = getCartWeekCount(selectedDate);
-  const localCount  = getLocalWeekCount(selectedDate);
-  const serverCount = getServerWeekCount(selectedDate);
+const toggleFavorite = async () => {
+  if (favoriteLoading.value) return;
 
-  return (serverCount + localCount + cartCount) >= 2;
+  const clientId = store.user?.client?.id;
+  if (!clientId) {
+    $q.notify({
+      message: t("scheduling_page.favorite_error"),
+      type: "negative",
+    });
+
+    return;
+  }
+
+  favoriteLoading.value = true;
+
+  try {
+    if (isFavorite.value) {
+      if (favoriteId.value) {
+        await deleteClientFavoriteProvider(favoriteId.value);
+      }
+
+      favoriteId.value = null;
+      isFavorite.value = false;
+
+      $q.notify({
+        message: t("scheduling_page.favorite_removed"),
+        type: "positive",
+      });
+    } else {
+      const created = await createClientFavoriteProvider({
+        client_id: clientId,
+        provider_id: props.provider.provider_id,
+      });
+
+      favoriteId.value = created?.id ?? null;
+      isFavorite.value = true;
+
+      $q.notify({
+        message: t("scheduling_page.favorite_added"),
+        type: "positive",
+      });
+    }
+  } catch {
+    $q.notify({
+      message: t("scheduling_page.favorite_error"),
+      type: "negative",
+    });
+  } finally {
+    favoriteLoading.value = false;
+  }
 };
 
 onMounted(() => {
   loadAvailability();
-  loadReviews();
   loadFavoriteState();
+  loadReviews();
 });
 </script>
 
@@ -545,7 +655,7 @@ onMounted(() => {
   display: flex;
   flex-direction: column;
   overflow: hidden;
-  background: #F9FAFB;
+  background: #f9fafb;
 }
 
 .dialog-header {
@@ -571,7 +681,7 @@ onMounted(() => {
   }
 
   :deep(.q-date__calendar-days .q-btn__content) {
-    color: #1E293B !important;
+    color: #1e293b !important;
   }
 
   // dias desabilitados: visíveis mas opacos
@@ -589,14 +699,18 @@ onMounted(() => {
 
   // cabeçalho dos dias (dom, seg, ter, qua...)
   :deep(.q-date__calendar-weekdays > div) {
-    color: #6366F1;
+    color: #6366f1;
     font-weight: 700;
     opacity: 0.8;
   }
 
   :deep(.q-date__navigation) {
-    .q-btn { color: #6366F1 !important; }
-    .q-btn__content { color: #6366F1 !important; }
+    .q-btn {
+      color: #6366f1 !important;
+    }
+    .q-btn__content {
+      color: #6366f1 !important;
+    }
   }
 
   :deep(.q-date__event) {
@@ -614,7 +728,7 @@ onMounted(() => {
 
   :deep(.q-date__selected .q-btn__content),
   :deep(.q-date__calendar-item .q-btn.q-date__selected .q-btn__content) {
-    background: #6366F1 !important;
+    background: #6366f1 !important;
     color: #ffffff !important;
     border-radius: 50%;
     box-shadow: 0 4px 10px rgba(99, 102, 241, 0.4);
@@ -622,7 +736,7 @@ onMounted(() => {
 
   :deep(.q-date__view--months .q-btn),
   :deep(.q-date__view--years .q-btn) {
-    color: #6366F1 !important;
+    color: #6366f1 !important;
   }
 
   :deep(.q-date__calendar-item--out) {
@@ -639,7 +753,9 @@ onMounted(() => {
   -webkit-overflow-scrolling: touch;
   scrollbar-width: none;
   padding-bottom: 8px;
-  &::-webkit-scrollbar { display: none; }
+  &::-webkit-scrollbar {
+    display: none;
+  }
 }
 
 .review-card {

+ 139 - 88
src/pages/search/components/SearchFilterDialog.vue

@@ -1,157 +1,197 @@
 <!-- eslint-disable @intlify/vue-i18n/no-raw-text -->
 <template>
-  <q-dialog ref="dialogRef" position="standard" @hide="onDialogHide">
+  <q-dialog
+    ref="dialogRef"
+    position="standard"
+    @hide="onDialogHide"
+  >
     <q-card class="filter-card bg-surface text-text">
-
       <q-card-section class="row items-center q-pb-none">
-        <span class="font16 fontbold gradient-diarista">{{ $t('search_filter.title') }}</span>
+        <span class="font16 fontbold gradient-diarista">{{
+          $t("search_filter.title")
+        }}</span>
+
         <q-space />
-        <q-btn flat round dense icon="mdi-close" color="grey-6" @click="onDialogCancel" />
+
+        <q-btn
+          color="grey-6"
+          dense
+          flat
+          icon="mdi-close"
+          round
+          @click="onDialogCancel"
+        />
       </q-card-section>
 
       <q-separator class="q-mt-sm" />
 
       <q-card-section class="q-pt-sm q-pb-none scroll filter-body">
+        <div class="font14 fontbold text-grey-7 q-mb-sm">
+          {{ $t("search_filter.sort_by") }}
+        </div>
 
-        <div class="font14 fontbold text-grey-7 q-mb-sm">{{ $t('search_filter.sort_by') }}</div>
+        <div
+          v-for="group in sortGroups"
+          :key="group.key"
+          class="q-mb-md"
+        >
+          <div class="font12 fontmedium text-text q-mb-xs">
+            {{ group.label }}
+          </div>
 
-        <div v-for="group in sortGroups" :key="group.key" class="q-mb-md">
-          <div class="font12 fontmedium text-text q-mb-xs">{{ group.label }}</div>
-          <div class="row q-gutter-x-lg">
+          <div class="sort-options">
             <q-radio
               v-for="opt in group.options"
               :key="opt.value"
               v-model="localSort"
-              :val="opt.value"
-              :label="opt.label"
               :disable="opt.disable ?? false"
+              :label="opt.label"
+              :val="opt.value"
+              class="sort-option font12 fontmedium"
               color="primary"
-              keep-color
-              class="font12 fontmedium"
               dense
+              keep-color
             />
           </div>
         </div>
 
         <q-separator class="q-my-sm" />
 
-        <div class="font12 fontbold text-grey-7 q-mb-xs">{{ $t('search_filter.filter_by') }}</div>
-        <div class="font12 fontmedium text-text q-mb-xs">{{ $t('search_filter.availability') }}</div>
+        <div class="font12 fontbold text-grey-7 q-mb-xs">
+          {{ $t("search_filter.filter_by") }}
+        </div>
+
+        <div class="font12 fontmedium text-text q-mb-xs">
+          {{ $t("search_filter.availability") }}
+        </div>
+
         <DefaultInputDatePicker
           v-model:untreated-date="localDate"
-          :label="$t('search_filter.availability_placeholder')"
-          dense
+          calendar-class="search-filter-calendar"
           class="date-picker-primary"
+          dense
+          :label="$t('search_filter.availability_placeholder')"
         />
       </q-card-section>
 
       <q-separator />
+
       <q-card-actions class="q-px-md q-py-md row q-gutter-x-sm">
         <q-btn
-          outline
+          class="col"
           color="grey-6"
-          :label="$t('search_filter.clear')"
-          rounded
           no-caps
+          outline
+          rounded
           size="md"
-          class="col"
+          :label="$t('search_filter.clear')"
           @click="clearFilters"
         />
+
         <q-btn
+          class="col"
           color="primary-button"
-          :label="$t('search_filter.apply')"
-          rounded
           no-caps
-          unelevated
+          rounded
           size="md"
-          class="col"
+          unelevated
+          :label="$t('search_filter.apply')"
           @click="applyFilters"
         />
       </q-card-actions>
-
     </q-card>
   </q-dialog>
 </template>
 
 <script setup>
-import { ref, computed } from 'vue';
-import { useDialogPluginComponent } from 'quasar';
-import { useI18n } from 'vue-i18n';
-import DefaultInputDatePicker from 'src/components/defaults/DefaultInputDatePicker.vue';
+import { computed, ref } from "vue";
+import { useDialogPluginComponent } from "quasar";
+import { useI18n } from "vue-i18n";
+
+import DefaultInputDatePicker from "src/components/defaults/DefaultInputDatePicker.vue";
 
 defineEmits([...useDialogPluginComponent.emits]);
 
-const { initialSort, initialDate } = defineProps({
-  initialSort: { type: String, default: null },
-  initialDate: { type: String, default: null },
+const { initialDate, initialSort } = defineProps({
+  initialDate: { default: null, type: String },
+  initialSort: { default: null, type: String },
 });
 
+const { dialogRef, onDialogCancel, onDialogHide, onDialogOK } =
+  useDialogPluginComponent();
 const { t } = useI18n();
-const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginComponent();
 
-const localSort = ref(initialSort ?? null);
 const localDate = ref(initialDate ?? null);
+const localSort = ref(initialSort ?? null);
 
 const sortGroups = computed(() => [
   {
-    key: 'price',
-    label: t('search_filter.groups.price'),
+    key: "price",
+    label: t("search_filter.groups.price"),
     options: [
-      { value: 'price_desc', label: t('search_filter.sort.higher') },
-      { value: 'price_asc',  label: t('search_filter.sort.lower') },
+      { value: "price_desc", label: t("search_filter.sort.higher") },
+      { value: "price_asc", label: t("search_filter.sort.lower") },
     ],
   },
   {
-    key: 'distance',
-    label: t('search_filter.groups.distance'),
+    key: "distance",
+    label: t("search_filter.groups.distance"),
     options: [
-      { value: 'distance_desc', label: t('search_filter.sort.higher'), disable: true },
-      { value: 'distance_asc',  label: t('search_filter.sort.lower'),  disable: true },
+      {
+        value: "distance_desc",
+        label: t("search_filter.sort.higher"),
+        disable: true,
+      },
+      {
+        value: "distance_asc",
+        label: t("search_filter.sort.lower"),
+        disable: true,
+      },
     ],
   },
   {
-    key: 'reviews',
-    label: t('search_filter.groups.reviews'),
+    key: "reviews",
+    label: t("search_filter.groups.reviews"),
     options: [
-      { value: 'reviews_desc', label: t('search_filter.sort.higher') },
-      { value: 'reviews_asc',  label: t('search_filter.sort.lower') },
+      { value: "reviews_desc", label: t("search_filter.sort.higher") },
+      { value: "reviews_asc", label: t("search_filter.sort.lower") },
     ],
   },
   {
-    key: 'rating',
-    label: t('search_filter.groups.rating'),
+    key: "rating",
+    label: t("search_filter.groups.rating"),
     options: [
-      { value: 'rating_desc', label: t('search_filter.sort.higher') },
-      { value: 'rating_asc',  label: t('search_filter.sort.lower') },
+      { value: "rating_desc", label: t("search_filter.sort.higher") },
+      { value: "rating_asc", label: t("search_filter.sort.lower") },
     ],
   },
   {
-    key: 'services',
-    label: t('search_filter.groups.services'),
+    key: "services",
+    label: t("search_filter.groups.services"),
     options: [
-      { value: 'services_desc', label: t('search_filter.sort.higher') },
-      { value: 'services_asc',  label: t('search_filter.sort.lower') },
+      { value: "services_desc", label: t("search_filter.sort.higher") },
+      { value: "services_asc", label: t("search_filter.sort.lower") },
     ],
   },
   {
-    key: 'oldest',
-    label: t('search_filter.groups.oldest'),
+    key: "oldest",
+    label: t("search_filter.groups.oldest"),
     options: [
-      { value: 'oldest',  label: t('search_filter.sort.oldest_asc') },
-      { value: 'newest',  label: t('search_filter.sort.oldest_desc') },
+      { value: "oldest", label: t("search_filter.sort.oldest_asc") },
+      { value: "newest", label: t("search_filter.sort.oldest_desc") },
     ],
   },
 ]);
 
-const clearFilters = () => {
-  localSort.value = null;
-  localDate.value = null;
-  onDialogOK({ sort: null, date: null });
-};
-
 const applyFilters = () => {
   onDialogOK({ sort: localSort.value, date: localDate.value });
 };
+
+const clearFilters = () => {
+  localDate.value = null;
+  localSort.value = null;
+  onDialogOK({ date: null, sort: null });
+};
 </script>
 
 <style scoped lang="scss">
@@ -159,18 +199,32 @@ const applyFilters = () => {
   border-radius: 16px;
   width: min(92vw, 420px);
 }
+
 .filter-body {
   max-height: 60dvh;
   overflow-y: auto;
 }
+
+.sort-options {
+  column-gap: 16px;
+  display: grid;
+  grid-template-columns: repeat(2, minmax(0, 1fr));
+}
+
+.sort-option {
+  width: 100%;
+}
+
 .date-picker-primary {
   :deep(.q-field__label) {
     color: var(--q-primary);
   }
+
   :deep(.q-field__native),
   :deep(.q-field__input) {
     color: var(--q-text, #555555) !important;
   }
+
   :deep(.q-icon) {
     color: var(--q-primary);
   }
@@ -178,33 +232,30 @@ const applyFilters = () => {
 </style>
 
 <style lang="scss">
-.q-popup-proxy {
-  .q-date {
-    background: var(--q-surface, #ffffff) !important;
-    color: var(--q-text, #555555) !important;
-    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.12);
-
-    .q-date__header {
-      background: var(--q-primary);
-      color: #fff;
-    }
+.search-filter-calendar {
+  background: #fff !important;
+  color: #555 !important;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.12);
 
-    .q-date__content {
-      color: var(--q-text, #555555) !important;
-    }
+  .q-date__header {
+    background: var(--q-primary);
+    color: #fff;
+  }
 
-    .q-date__calendar-item .q-btn {
-      color: var(--q-text, #555555) !important;
-    }
+  .q-date__content,
+  .q-date__navigation .q-btn,
+  .q-date__navigation .q-btn__content,
+  .q-date__calendar-weekdays > div {
+    color: #555 !important;
+  }
 
-    .q-date__navigation .q-btn {
-      color: var(--q-text, #555555) !important;
-    }
+  .q-date__calendar-days .q-btn:not(.bg-primary),
+  .q-date__calendar-days .q-btn:not(.bg-primary) .q-btn__content {
+    color: #555 !important;
+  }
 
-    .q-date__calendar-weekdays > div {
-      color: var(--q-text, #555555) !important;
-      opacity: 0.6;
-    }
+  .q-date__calendar-weekdays > div {
+    opacity: 0.6;
   }
 }
-</style>
+</style>

+ 80 - 64
src/pages/search/components/ServiceSelectionSheet.vue

@@ -21,8 +21,11 @@
       <q-separator class="q-mt-sm" />
 
       <q-card-section class="q-pt-sm q-pb-md">
-        <div v-if="availableServiceTypes.length === 0" class="text-center text-grey-6 text-body2 q-py-md">
-          {{ $t('scheduling_page.no_slots_available') }}
+        <div
+          v-if="availableServiceTypes.length === 0"
+          class="text-center text-grey-6 text-body2 q-py-md"
+        >
+          {{ $t("scheduling_page.no_slots_available") }}
         </div>
 
         <div
@@ -56,7 +59,7 @@
           </div> -->
 
           <div class="row no-wrap q-gutter-x-xs">
-             <q-btn
+            <q-btn
               color="secondary"
               no-caps
               padding="2px 16px"
@@ -64,12 +67,7 @@
               size="sm"
               unelevated
               :label="$t('common.actions.select')"
-              @click="onDialogOK({
-                action: 'cart',
-                serviceType: type,
-                date: selectedDate,
-                provider
-              })"
+              @click="onDialogOK({ action: 'cart', serviceType: type, date: selectedDate, provider })"
             />
           </div>
         </div>
@@ -79,85 +77,103 @@
 </template>
 
 <script setup>
-import { computed } from 'vue';
-import { useDialogPluginComponent, useQuasar } from 'quasar';
-import { useI18n } from 'vue-i18n';
-import ServiceTypeInfoDialog from './ServiceTypeInfoDialog.vue';
+import { computed } from "vue";
+import { useDialogPluginComponent, useQuasar } from "quasar";
+import { useI18n } from "vue-i18n";
+
+import ServiceTypeInfoDialog from "./ServiceTypeInfoDialog.vue";
+
+defineEmits([...useDialogPluginComponent.emits]);
 
 const props = defineProps({
-  partialBlocks: { type: Array,  required: false, default: () => [] },
-  provider:      { type: Object, required: true },
-  selectedDate:  { type: String, required: true },
+  partialBlocks: { default: () => [], required: false, type: Array },
+  provider: { required: true, type: Object },
+  selectedDate: { required: true, type: String },
 });
 
-defineEmits([...useDialogPluginComponent.emits]);
+const $q = useQuasar();
 
-const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginComponent();
+const { dialogRef, onDialogCancel, onDialogHide, onDialogOK } =
+  useDialogPluginComponent();
 
-const $q    = useQuasar();
 const { t } = useI18n();
 
+const slotConflicts = (slotStart, slotEnd, blocks) =>
+  blocks.some((b) => {
+    const blockEnd = parseInt(b.end_hour);
+    const blockStart = parseInt(b.init_hour);
+
+    return slotEnd >= blockStart && slotStart <= blockEnd;
+  });
+
+const hasValidSlots = (hoursCount) => {
+  for (let s = 7; s + hoursCount <= 20; s++) {
+    if (!slotConflicts(s, s + hoursCount, props.partialBlocks)) return true;
+  }
+  return false;
+};
+
 const availableServiceTypes = computed(() =>
   [
     {
-      description: t('scheduling_page.service_types.integral.description'),
-      hours:       t('scheduling_page.service_types.integral.hours'),
-      hoursCount:  8,
-      key:         'integral',
-      label:       t('scheduling_page.service_types.integral.label'),
-      basePrice:   props.provider?.daily_price_8h_base ?? props.provider?.daily_price_8h ?? null,
-      price:       props.provider?.daily_price_8h ?? null,
+      description: t("scheduling_page.service_types.integral.description"),
+      hours: t("scheduling_page.service_types.integral.hours"),
+      hoursCount: 8,
+      key: "integral",
+      label: t("scheduling_page.service_types.integral.label"),
+      basePrice:
+        props.provider?.daily_price_8h_base ??
+        props.provider?.daily_price_8h ??
+        null,
+      price: props.provider?.daily_price_8h ?? null,
     },
     {
-      description: t('scheduling_page.service_types.padrao.description'),
-      hours:       t('scheduling_page.service_types.padrao.hours'),
-      hoursCount:  6,
-      key:         'padrao',
-      label:       t('scheduling_page.service_types.padrao.label'),
-      basePrice:   props.provider?.daily_price_6h_base ?? props.provider?.daily_price_6h ?? null,
-      price:       props.provider?.daily_price_6h ?? null,
+      description: t("scheduling_page.service_types.padrao.description"),
+      hours: t("scheduling_page.service_types.padrao.hours"),
+      hoursCount: 6,
+      key: "padrao",
+      label: t("scheduling_page.service_types.padrao.label"),
+      basePrice:
+        props.provider?.daily_price_6h_base ??
+        props.provider?.daily_price_6h ??
+        null,
+      price: props.provider?.daily_price_6h ?? null,
     },
     {
-      description: t('scheduling_page.service_types.meio_periodo.description'),
-      hours:       t('scheduling_page.service_types.meio_periodo.hours'),
-      hoursCount:  4,
-      key:         'meio_periodo',
-      label:       t('scheduling_page.service_types.meio_periodo.label'),
-      basePrice:   props.provider?.daily_price_4h_base ?? props.provider?.daily_price_4h ?? null,
-      price:       props.provider?.daily_price_4h ?? null,
+      description: t("scheduling_page.service_types.meio_periodo.description"),
+      hours: t("scheduling_page.service_types.meio_periodo.hours"),
+      hoursCount: 4,
+      key: "meio_periodo",
+      label: t("scheduling_page.service_types.meio_periodo.label"),
+      basePrice:
+        props.provider?.daily_price_4h_base ??
+        props.provider?.daily_price_4h ??
+        null,
+      price: props.provider?.daily_price_4h ?? null,
     },
     {
-      description: t('scheduling_page.service_types.diaria_rapida.description'),
-      hours:       t('scheduling_page.service_types.diaria_rapida.hours'),
-      hoursCount:  2,
-      key:         'diaria_rapida',
-      label:       t('scheduling_page.service_types.diaria_rapida.label'),
-      basePrice:   props.provider?.daily_price_2h_base ?? props.provider?.daily_price_2h ?? null,
-      price:       props.provider?.daily_price_2h ?? null,
+      description: t("scheduling_page.service_types.diaria_rapida.description"),
+      hours: t("scheduling_page.service_types.diaria_rapida.hours"),
+      hoursCount: 2,
+      key: "diaria_rapida",
+      label: t("scheduling_page.service_types.diaria_rapida.label"),
+      basePrice:
+        props.provider?.daily_price_2h_base ??
+        props.provider?.daily_price_2h ??
+        null,
+      price: props.provider?.daily_price_2h ?? null,
     },
-  ].filter(type => hasValidSlots(type.hoursCount))
+  ].filter((type) => hasValidSlots(type.hoursCount)),
 );
 
 // const formatPrice = (value) =>Number(value).toLocaleString('pt-BR', { style: 'currency', currency: 'BRL' });
 
-const hasValidSlots = (hoursCount) => {
-  for (let s = 7; s + hoursCount <= 20; s++) {
-    if (!slotConflicts(s, s + hoursCount, props.partialBlocks)) return true;
-  }
-  return false;
-};
-
 const openInfo = (type) => {
-  $q.dialog({ component: ServiceTypeInfoDialog, componentProps: { serviceType: type } });
-};
-
-const slotConflicts = (slotStart, slotEnd, blocks) =>
-  blocks.some(b => {
-    const blockEnd   = parseInt(b.end_hour);
-    const blockStart = parseInt(b.init_hour);
-
-    return slotEnd >= blockStart && slotStart <= blockEnd;
+  $q.dialog({
+    component: ServiceTypeInfoDialog,
+    componentProps: { serviceType: type },
   });
+};
 </script>
 
 <style scoped lang="scss">

+ 50 - 36
src/pages/search/components/ServiceTimeSelectionDialog.vue

@@ -1,7 +1,11 @@
 <template>
-  <q-dialog ref="dialogRef" @hide="onDialogHide">
-    <q-card class="card-border bg-surface text-text time-card relative-position">
-
+  <q-dialog
+    ref="dialogRef"
+    @hide="onDialogHide"
+  >
+    <q-card
+      class="card-border bg-surface text-text time-card relative-position"
+    >
       <q-btn
         class="absolute-top-right close-button q-mt-sm q-mr-sm"
         color="grey-5"
@@ -18,12 +22,12 @@
           {{ serviceType.label }}
 
           <span class="text-grey-6">
-            {{ '(' + serviceType.hoursCount + 'h)' }}
+            {{ "(" + serviceType.hoursCount + "h)" }}
           </span>
         </div>
 
         <div class="font14 fontmedium text-grey-6 q-mt-xs">
-          {{ $t('scheduling_page.time_selection.subtitle') }}
+          {{ $t("scheduling_page.time_selection.subtitle") }}
         </div>
       </q-card-section>
 
@@ -52,7 +56,7 @@
 
         <q-card-section class="q-py-sm">
           <div class="font14 fontbold text-text q-mb-sm text-center">
-            {{ $t('scheduling_page.time_selection.meal_section') }}
+            {{ $t("scheduling_page.time_selection.meal_section") }}
           </div>
 
           <div class="meal-options column items-start">
@@ -93,7 +97,10 @@
         />
       </q-card-actions>
 
-      <div v-if="pauseNote" class="text-center font12 fontmedium text-primary q-pb-md">
+      <div
+        v-if="pauseNote"
+        class="text-center font12 fontmedium text-primary q-pb-md"
+      >
         {{ pauseNote }}
       </div>
     </q-card>
@@ -101,9 +108,11 @@
 </template>
 
 <script setup>
-import { useDialogPluginComponent } from 'quasar';
-import { computed, ref } from 'vue';
-import { useI18n } from 'vue-i18n';
+import { computed, ref } from "vue";
+import { useDialogPluginComponent } from "quasar";
+import { useI18n } from "vue-i18n";
+
+defineEmits([...useDialogPluginComponent.emits]);
 
 const props = defineProps({
   partialBlocks: { default: () => [], required: false, type: Array },
@@ -112,47 +121,32 @@ const props = defineProps({
   serviceType: { required: true, type: Object },
 });
 
-defineEmits([...useDialogPluginComponent.emits]);
-
 const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent();
-
 const { t } = useI18n();
 
 const selectedMeal = ref(null);
 const selectedSlot = ref(null);
 
-const handleClose = () => {
-  dialogRef.value?.hide();
-};
-
-const handleContinue = () => {
-  const slotObj = timeSlots.value.find(s => s.value === selectedSlot.value);
+const slotConflicts = (slotStart, slotEnd, blocks) =>
+  blocks.some((block) => {
+    const blockEnd = parseInt(block.end_hour);
+    const blockStart = parseInt(block.init_hour);
 
-  onDialogOK({
-    serviceType: props.serviceType,
-    date: props.selectedDate,
-    slot: { value: selectedSlot.value, startHour: slotObj.startHour, endHour: slotObj.endHour },
-    meal: selectedMeal.value,
+    return slotEnd >= blockStart && slotStart <= blockEnd;
   });
-};
 
-const hasMealSection = computed(() =>
-  props.serviceType.hoursCount >= 6
-);
+const hasMealSection = computed(() => props.serviceType.hoursCount >= 6);
 
 const pauseNote = computed(() => {
-  const map = { 8: t('scheduling_page.time_selection.pause_note_8h'), 6: t('scheduling_page.time_selection.pause_note_6h'), 4: t('scheduling_page.time_selection.pause_note_4h') };
+  const map = {
+    8: t("scheduling_page.time_selection.pause_note_8h"),
+    6: t("scheduling_page.time_selection.pause_note_6h"),
+    4: t("scheduling_page.time_selection.pause_note_4h"),
+  };
 
   return map[props.serviceType.hoursCount] ?? null;
 });
 
-const slotConflicts = (slotStart, slotEnd, blocks) =>
-  blocks.some(b => {
-    const blockStart = parseInt(b.init_hour);
-    const blockEnd = parseInt(b.end_hour);
-    return slotEnd >= blockStart && slotStart <= blockEnd;
-  });
-
 const timeSlots = computed(() => {
   const h = props.serviceType.hoursCount;
 
@@ -173,6 +167,26 @@ const timeSlots = computed(() => {
   return slots;
 });
 
+const handleClose = () => {
+  dialogRef.value?.hide();
+};
+
+const handleContinue = () => {
+  const slotObj = timeSlots.value.find(
+    (slot) => slot.value === selectedSlot.value,
+  );
+
+  onDialogOK({
+    date: props.selectedDate,
+    meal: selectedMeal.value,
+    serviceType: props.serviceType,
+    slot: {
+      endHour: slotObj.endHour,
+      startHour: slotObj.startHour,
+      value: selectedSlot.value,
+    },
+  });
+};
 </script>
 
 <style scoped lang="scss">

+ 23 - 10
src/pages/search/components/ServiceTypeInfoDialog.vue

@@ -1,11 +1,25 @@
 <template>
-  <q-dialog ref="dialogRef" @hide="onDialogHide">
-    <q-card class="card-border bg-surface text-text" style="width: min(88vw, 360px);">
-
+  <q-dialog
+    ref="dialogRef"
+    @hide="onDialogHide"
+  >
+    <q-card
+      class="card-border bg-surface text-text"
+      style="width: min(88vw, 360px)"
+    >
       <q-card-section class="row items-center q-pb-none">
         <div class="font16 fontbold text-text">{{ serviceType.label }}</div>
+
         <q-space />
-        <q-btn flat round dense icon="mdi-close" color="grey-6" @click="onDialogCancel" />
+
+        <q-btn
+          color="grey-6"
+          dense
+          flat
+          icon="mdi-close"
+          round
+          @click="onDialogCancel"
+        />
       </q-card-section>
 
       <q-card-section class="font14 fontmedium text-grey-7 q-pt-xs">
@@ -17,22 +31,21 @@
       <q-card-section class="font12 fontmedium text-text">
         {{ serviceType.description }}
       </q-card-section>
-
     </q-card>
   </q-dialog>
 </template>
 
 <script setup>
-import { useDialogPluginComponent } from 'quasar';
+import { useDialogPluginComponent } from "quasar";
+
+defineEmits([...useDialogPluginComponent.emits]);
 
 defineProps({
   serviceType: {
-    type: Object,
     required: true,
+    type: Object,
   },
 });
 
-defineEmits([...useDialogPluginComponent.emits]);
-
-const { dialogRef, onDialogHide, onDialogCancel } = useDialogPluginComponent();
+const { dialogRef, onDialogCancel, onDialogHide } = useDialogPluginComponent();
 </script>

+ 0 - 21
src/router/routes/schedule-cart.route.js

@@ -1,21 +0,0 @@
-export default [
-  {
-    path: "carrinho",
-    name: "ScheduleCartPage",
-    component: () => import("src/pages/schedule-cart/ScheduleCartPage.vue"),
-    meta: {
-      title: "schedule_cart.title",
-      requireAuth: true,
-      breadcrumbs: [
-        {
-          name: "DashboardPage",
-          title: "ui.navigation.dashboard",
-        },
-        {
-          name: "ScheduleCartPage",
-          title: "schedule_cart.title",
-        },
-      ],
-    },
-  },
-];

+ 57 - 34
src/stores/registrationFlow.js

@@ -1,22 +1,24 @@
-import { defineStore } from 'pinia';
-import { ref } from 'vue';
+import { defineStore } from "pinia";
+import { ref } from "vue";
 
-export const useRegistrationFlowStore = defineStore('registrationFlow', () => {
-  const email = ref('');
-  const phone = ref('');
-  const code = ref('');
+export const useRegistrationFlowStore = defineStore("registrationFlow", () => {
+  const email = ref("");
+  const phone = ref("");
+  const code = ref("");
+  const document = ref("");
+  const name = ref("");
 
   const initialLat = ref(null);
   const initialLng = ref(null);
 
   const confirmedLat = ref(null);
   const confirmedLng = ref(null);
-  const confirmedAddress = ref('');
-  const confirmedNumber = ref('');
-  const confirmedDistrict = ref('');
-  const confirmedCity = ref('');
-  const confirmedState = ref('');
-  const confirmedZipCode = ref('');
+  const confirmedAddress = ref("");
+  const confirmedNumber = ref("");
+  const confirmedDistrict = ref("");
+  const confirmedCity = ref("");
+  const confirmedState = ref("");
+  const confirmedZipCode = ref("");
 
   const setCredentials = (e, p, c) => {
     email.value = e;
@@ -29,45 +31,66 @@ export const useRegistrationFlowStore = defineStore('registrationFlow', () => {
     initialLng.value = lng;
   };
 
+  const setRegistrationData = (data) => {
+    document.value = data.document ?? "";
+    name.value = data.name ?? "";
+    phone.value = data.phone ?? phone.value;
+  };
+
   const setConfirmedLocation = (data) => {
     confirmedLat.value = data.lat;
     confirmedLng.value = data.lng;
-    confirmedAddress.value = data.address ?? '';
-    confirmedNumber.value = data.number ?? '';
-    confirmedDistrict.value = data.district ?? '';
-    confirmedCity.value = data.city ?? '';
-    confirmedState.value = data.state ?? '';
-    confirmedZipCode.value = data.zip_code ?? '';
+    confirmedAddress.value = data.address ?? "";
+    confirmedNumber.value = data.number ?? "";
+    confirmedDistrict.value = data.district ?? "";
+    confirmedCity.value = data.city ?? "";
+    confirmedState.value = data.state ?? "";
+    confirmedZipCode.value = data.zip_code ?? "";
   };
 
   const clear = () => {
-    email.value = '';
-    phone.value = '';
-    code.value = '';
+    email.value = "";
+    phone.value = "";
+    code.value = "";
+    document.value = "";
+    name.value = "";
     initialLat.value = null;
     initialLng.value = null;
     confirmedLat.value = null;
     confirmedLng.value = null;
-    confirmedAddress.value = '';
-    confirmedNumber.value = '';
-    confirmedDistrict.value = '';
-    confirmedCity.value = '';
-    confirmedState.value = '';
-    confirmedZipCode.value = '';
+    confirmedAddress.value = "";
+    confirmedNumber.value = "";
+    confirmedDistrict.value = "";
+    confirmedCity.value = "";
+    confirmedState.value = "";
+    confirmedZipCode.value = "";
   };
 
   const hasCredentials = () => !!(email.value || phone.value) && !!code.value;
-  const hasInitialLocation = () => initialLat.value !== null && initialLng.value !== null;
-  const hasConfirmedLocation = () => confirmedLat.value !== null && confirmedLng.value !== null;
+  const hasInitialLocation = () =>
+    initialLat.value !== null && initialLng.value !== null;
+  const hasConfirmedLocation = () =>
+    confirmedLat.value !== null && confirmedLng.value !== null;
 
   return {
-    email, phone, code,
-    initialLat, initialLng,
-    confirmedLat, confirmedLng,
-    confirmedAddress, confirmedNumber, confirmedDistrict,
-    confirmedCity, confirmedState, confirmedZipCode,
+    email,
+    phone,
+    code,
+    document,
+    name,
+    initialLat,
+    initialLng,
+    confirmedLat,
+    confirmedLng,
+    confirmedAddress,
+    confirmedNumber,
+    confirmedDistrict,
+    confirmedCity,
+    confirmedState,
+    confirmedZipCode,
     setCredentials,
     setInitialLocation,
+    setRegistrationData,
     setConfirmedLocation,
     clear,
     hasCredentials,

+ 0 - 33
src/stores/scheduleCart.js

@@ -1,33 +0,0 @@
-import { defineStore } from "pinia";
-import { ref } from "vue";
-
-export const useScheduleCartStore = defineStore("scheduleCart", () => {
-  const items = ref([]);
-
-  const lastCreatedGroup = ref(null);
-
-  const addItem = (item) => {
-    items.value.push(item);
-  };
-
-  const removeItem = (index) => {
-    items.value.splice(index, 1);
-  };
-
-  const clear = () => {
-    items.value = [];
-  };
-
-  const setLastCreatedGroup = (group) => {
-    lastCreatedGroup.value = group;
-  };
-
-  return {
-    items,
-    lastCreatedGroup,
-    addItem,
-    removeItem,
-    clear,
-    setLastCreatedGroup,
-  };
-});