Просмотр исходного кода

feat: add genero no cadastro do prestador + add condicional para renderizar componente de pix saque (cadastro de chave) + add condicional para renderizar campo telefone como opcao de login

Gustavo Mantovani 1 неделя назад
Родитель
Сommit
00d0a16d0b

+ 5 - 0
src/api/providerWithdrawal.js

@@ -5,6 +5,11 @@ export const getBalance = async () => {
   return data.payload
 }
 
+export const getWithdrawalConfiguration = async () => {
+  const { data } = await api.get('/provider/withdrawals/config')
+  return data.payload
+}
+
 export const getWithdrawalFees = async () => {
   const { data } = await api.get('/provider/withdrawals/fees')
   return data.payload

+ 7 - 0
src/api/serviceConfiguration.js

@@ -0,0 +1,7 @@
+import api from "src/api";
+
+export const getServiceConfiguration = async () => {
+  const { data } = await api.get("/services/config");
+
+  return data.payload;
+};

+ 23 - 13
src/components/dashboard/DashboardNextSchedules.vue

@@ -5,7 +5,11 @@
     </div>
 
     <div v-if="!data.length" class="empty-alert row no-wrap items-start">
-      <q-icon class="empty-alert__icon" name="mdi-alert-outline" size="28px" />
+      <q-icon
+        class="empty-alert__icon"
+        name="mdi-alert-outline"
+        size="28px"
+      />
 
       <div class="empty-alert__text font10">
         <span class="fontbold">
@@ -16,7 +20,10 @@
       </div>
     </div>
 
-    <div v-else class="scroll-wrapper">
+    <div
+      v-else
+      class="scroll-wrapper"
+    >
       <div class="scroll-track">
         <q-card
           v-for="item in data"
@@ -53,8 +60,15 @@
                     {{ getFirstName(item.customer_name ?? item.client_name) || "—" }}
                   </span>
 
-                  <div v-if="item.customer_rating" class="row items-center">
-                    <q-icon color="warning" name="mdi-star" size="14px" />
+                  <div
+                    v-if="item.customer_rating"
+                    class="row items-center"
+                  >
+                    <q-icon
+                      color="warning"
+                      name="mdi-star"
+                      size="14px"
+                    />
 
                     <span class="text-text font11">
                       {{ item.customer_rating }}
@@ -93,13 +107,7 @@
                 </div>
 
                 <div class="font10 fontmedium text-type">
-                  {{
-                    t(
-                      labelsPeriodTypes.find(
-                        (label) => label.value == item.period_type,
-                      )?.label,
-                    )
-                  }}
+                  {{ t(labelsPeriodTypes.find((label) => label.value == item.period_type)?.label) }}
                 </div>
 
                 <div class="font10 text-distance">
@@ -165,7 +173,7 @@
 
 <script setup>
 import { formatAddress, formatCurrency, getFirstName } from "src/helpers/utils";
-import { labelsPeriodTypes } from "src/helpers/arraysOptions/labelsPeriodTypes.js";
+import { labelsPeriodTypes } from "src/helpers/labelsPeriodTypes.js";
 import { useI18n } from "vue-i18n";
 import { useQuasar } from "quasar";
 
@@ -179,12 +187,13 @@ defineProps({
 const emit = defineEmits(["view-details"]);
 
 const $q = useQuasar();
+
 const { t } = useI18n();
 
 const avatarColors = [
   { background: "#ffd5df", color: "#932e57" },
   { background: "#d7e8ff", color: "#2158a8" },
-  { background: "#dfd", color: "#2a7a3b" },
+  { background: "#dfd",    color: "#2a7a3b" },
   { background: "#ffe5cc", color: "#8a4500" },
 ];
 
@@ -218,6 +227,7 @@ const formatWeekday = (iso) => {
   }
 
   const date = new Date(iso);
+
   const weekday = date.toLocaleDateString("pt-BR", { weekday: "long" });
 
   return weekday.charAt(0).toUpperCase() + weekday.slice(1);

+ 19 - 12
src/components/dashboard/DashboardOpportunities.vue

@@ -16,8 +16,15 @@
       />
     </div>
 
-    <div v-if="!data.length" class="empty-alert row no-wrap items-start">
-      <q-icon class="empty-alert__icon" name="mdi-alert-outline" size="28px" />
+    <div
+      v-if="!data.length"
+      class="empty-alert row no-wrap items-start"
+    >
+      <q-icon
+        class="empty-alert__icon"
+        name="mdi-alert-outline"
+        size="28px"
+      />
 
       <div class="empty-alert__text font10">
         <span class="fontbold">
@@ -67,7 +74,11 @@
 
                   <div class="row items-center">
                     <div class="q-my-auto">
-                      <q-icon color="warning" name="mdi-star" size="12px" />
+                      <q-icon
+                        color="warning"
+                        name="mdi-star"
+                        size="12px"
+                      />
                     </div>
 
                     <div class="q-my-auto">
@@ -109,13 +120,7 @@
                 </div>
 
                 <div class="font9 text-type">
-                  {{
-                    $t(
-                      labelsPeriodTypes.find(
-                        (label) => label.value == item.period_type,
-                      )?.label,
-                    )
-                  }}
+                  {{ $t(labelsPeriodTypes.find((label) => label.value == item.period_type)?.label) }}
                 </div>
 
                 <div class="font9 text-region">
@@ -152,7 +157,7 @@
 
 <script setup>
 import { formatCurrency, getFirstName } from "src/helpers/utils";
-import { labelsPeriodTypes } from "src/helpers/arraysOptions/labelsPeriodTypes.js";
+import { labelsPeriodTypes } from "src/helpers/labelsPeriodTypes.js";
 import { useQuasar } from "quasar";
 import { useRouter } from "vue-router";
 
@@ -166,12 +171,13 @@ defineProps({
 });
 
 const $q = useQuasar();
+
 const router = useRouter();
 
 const avatarColors = [
   { background: "#ffd5df", color: "#932e57" },
   { background: "#d7e8ff", color: "#2158a8" },
-  { background: "#dfd", color: "#2a7a3b" },
+  { background: "#dfd",    color: "#2a7a3b" },
   { background: "#ffe5cc", color: "#8a4500" },
 ];
 
@@ -192,6 +198,7 @@ const formatWeekday = (iso) => {
   }
 
   const date = new Date(iso);
+
   const weekday = date.toLocaleDateString("pt-BR", { weekday: "long" });
 
   return weekday.charAt(0).toUpperCase() + weekday.slice(1);

+ 134 - 58
src/components/dashboard/DashboardSolicitations.vue

@@ -1,88 +1,148 @@
 <template>
-  <div class="q-mx-md q-mb-md">
-    <div class="row items-center justify-between q-mb-sm">
-      <div class="section-title font16 fontbold gradient-diarista">{{ $t('provider.dashboard.solicitations.title') }}</div>
-      <q-btn flat no-caps color="grey-6" icon="mdi-chevron-right-outlined" size="sm" :label="$t('common.see_all')" />
+  <div class="q-mb-md q-mx-md">
+    <div class="items-center justify-between q-mb-sm row">
+      <div class="font16 fontbold gradient-diarista section-title">
+        {{ $t("provider.dashboard.solicitations.title") }}
+      </div>
+
+      <q-btn
+        color="grey-6"
+        flat
+        icon="mdi-chevron-right-outlined"
+        no-caps
+        size="sm"
+        :label="$t('common.see_all')"
+      />
     </div>
 
     <div class="scroll-wrapper">
-      <div class="scroll-track row no-wrap q-gutter-x-md">
+      <div class="no-wrap q-gutter-x-md row scroll-track">
         <q-card
           v-for="item in data"
           :key="item.id"
-          class="solicitation-card bg-surface q-ma-md shadow-card card-border"
           :flat="false"
+          class="bg-surface card-border q-ma-md shadow-card solicitation-card"
         >
           <q-card-section class="q-pa-sm">
-            <div class="row no-wrap items-center q-gutter-x-sm text-text">
+            <div class="items-center no-wrap q-gutter-x-sm row text-text">
               <q-avatar size="48px">
-                <img v-if="item.customer_photo" :src="item.customer_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.client_name)?.slice(0, 2).toUpperCase() ?? '??' }}</span>
+                <img
+                  v-if="item.customer_photo"
+                  :src="item.customer_photo"
+                  style="object-fit: cover"
+                />
+
+                <span
+                  v-else
+                  class="flex flex-center full-height full-width"
+                  style="border-radius: 50%"
+                  :style="avatarColors[item.id % avatarColors.length]"
+                >
+                  {{
+                    getFirstName(item.client_name)
+                      ?.slice(0, 2)
+                      .toUpperCase() ?? "??"
+                  }}
+                </span>
               </q-avatar>
+
               <div class="column flex-1">
-                <div class="row items-center q-gutter-x-xs">
-                  <span class="ellipsis font9 fontmedium">{{ getFirstName(item.client_name) || '—' }}</span>
-                  <div class="row items-center">
-                    <q-icon name="mdi-star" color="warning" size="12px" />
-                    <span class="text-text font9">{{ item.average_rating }}</span>
+                <div class="items-center q-gutter-x-xs row">
+                  <span class="ellipsis font9 fontmedium">
+                    {{ getFirstName(item.client_name) || "—" }}
+                  </span>
+
+                  <div class="items-center row">
+                    <q-icon
+                      color="warning"
+                      name="mdi-star"
+                      size="12px"
+                    />
+
+                    <span class="font9 text-text">
+                      {{ item.average_rating }}
+                    </span>
                   </div>
                 </div>
-                <div class="row items-center no-wrap font9">
-                  <span class="fontbold">{{ formatWeekday(item.date) }}</span>
-                  <span class="">{{ ', ' + formatDayMonth(item.date) }}</span>
+
+                <div class="font9 items-center no-wrap row">
+                  <span class="fontbold">
+                    {{ formatWeekday(item.date) }}
+                  </span>
+
+                  <span>{{ `, ${formatDayMonth(item.date)}` }}</span>
                 </div>
+
                 <div class="font9">
-                  {{ $t('common.from') }}
+                  {{ $t("common.from") }}
+
                   <span class="fontbold">
                     {{ item.start_time?.slice(0, 5) }}
-                    {{ $t('common.to') }}
+                    {{ $t("common.to") }}
                     {{ item.end_time?.slice(0, 5) }}
                   </span>
                 </div>
               </div>
+
               <div class="column items-end text-text">
-                <div class="font12 fontbold">{{ formatCurrency(item.total_amount) }}</div>
-                <div class="font9">{{ t(labelsPeriodTypes.find(label => label.value == item.period_type)?.label) }}</div>
-                <div class="font9 fontbold">{{ item.address?.district || 'N/A' }}</div>
+                <div class="font12 fontbold">
+                  {{ formatCurrency(item.total_amount) }}
+                </div>
+
+                <div class="font9">
+                  {{ t(labelsPeriodTypes.find((label) => label.value == item.period_type)?.label) }}
+                </div>
+
+                <div class="font9 fontbold">
+                  {{ item.address?.district || "N/A" }}
+                </div>
+
                 <div class="font9">
-                  <span class="q-pr-xs">{{ item.distance_km ?? 0 }}</span>
-                  {{ $t('common.km') }}
+                  <span class="q-pr-xs">
+                    {{ item.distance_km ?? 0 }}
+                  </span>
+
+                  {{ $t("common.km") }}
                 </div>
               </div>
             </div>
 
-            <div class="row q-mt-sm q-gutter-x-sm items-center">
+            <div class="items-center q-gutter-x-sm q-mt-sm row">
               <div>
-                <div class="text-text font9 fontmedium">
+                <div class="font9 fontmedium text-text">
                   {{ item.time_since_request }}
                 </div>
+
                 <q-btn
+                  class="col-auto q-px-none"
+                  color="primary"
                   flat
                   no-caps
-                  color="primary"
                   size="xs"
-                  class="col-auto q-px-none"
                   :label="$t('common.details')"
                   @click="emit('view-details', item)"
                 />
               </div>
+
               <q-space />
+
               <q-btn
-                unelevated
-                rounded
+                class="bg-grey-3 btn-action col-auto text-grey-8"
                 no-caps
-                class="col-auto bg-grey-3 text-grey-8 btn-action"
+                rounded
                 size="sm"
+                unelevated
                 :label="$t('common.refuse')"
                 @click="emit('reject', item)"
               />
+
               <q-btn
-                unelevated
-                rounded
-                no-caps
+                class="btn-action col-auto"
                 color="secondary"
-                class="col-auto btn-action"
+                no-caps
+                rounded
                 size="sm"
+                unelevated
                 :label="$t('common.accept')"
                 @click="emit('accept', item)"
               />
@@ -95,62 +155,78 @@
 </template>
 
 <script setup>
-import { formatCurrency, getFirstName } from 'src/helpers/utils';
-import { useI18n } from 'vue-i18n';
-import { labelsPeriodTypes } from 'src/helpers/arraysOptions/labelsPeriodTypes.js';
+import { formatCurrency, getFirstName } from "src/helpers/utils";
+import { labelsPeriodTypes } from "src/helpers/labelsPeriodTypes.js";
+import { useI18n } from "vue-i18n";
 
 defineProps({
   data: {
+    default: () => [],
     type: Array,
-    default: () => []
-  }
+  },
 });
 
-const emit = defineEmits(['accept', 'reject', 'view-details']);
+const emit = defineEmits(["accept", "reject", "view-details"]);
 
-const t = useI18n().t;
+const { t } = useI18n();
 
 const avatarColors = [
-  { background: '#ffd5df', color: '#932e57' },
-  { background: '#d7e8ff', color: '#2158a8' },
-  { background: '#dfd',    color: '#2a7a3b' },
-  { background: '#ffe5cc', color: '#8a4500' },
+  { background: "#ffd5df", color: "#932e57" },
+  { background: "#d7e8ff", color: "#2158a8" },
+  { background: "#dfd",    color: "#2a7a3b" },
+  { background: "#ffe5cc", color: "#8a4500" },
 ];
 
 const parseLocalDate = (iso) => {
   if (!iso) return null;
-  const m = String(iso).match(/^(\d{4})-(\d{2})-(\d{2})/);
-  return m ? new Date(+m[1], +m[2] - 1, +m[3]) : null;
-};
 
-const formatWeekday = (iso) => {
-  const d = parseLocalDate(iso);
-  if (!d) return '';
-  const w = d.toLocaleDateString('pt-BR', { weekday: 'long' });
-  return w.charAt(0).toUpperCase() + w.slice(1);
+  const matches = String(iso).match(/^(\d{4})-(\d{2})-(\d{2})/);
+
+  return matches
+    ? new Date(+matches[1], +matches[2] - 1, +matches[3])
+    : null;
 };
 
 const formatDayMonth = (iso) => {
-  const d = parseLocalDate(iso);
-  if (!d) return '';
-  return d.toLocaleDateString('pt-BR', { day: '2-digit', month: '2-digit' });
+  const date = parseLocalDate(iso);
+
+  if (!date) return "";
+
+  return date.toLocaleDateString("pt-BR", {
+    day: "2-digit",
+    month: "2-digit",
+  });
+};
+
+const formatWeekday = (iso) => {
+  const date = parseLocalDate(iso);
+
+  if (!date) return "";
+
+  const weekday = date.toLocaleDateString("pt-BR", { weekday: "long" });
+
+  return weekday.charAt(0).toUpperCase() + weekday.slice(1);
 };
 </script>
 
-<style scoped lang="scss">
+<style lang="scss" scoped>
 .scroll-wrapper {
   overflow-x: auto;
   padding-bottom: 8px;
+
   &::-webkit-scrollbar {
     display: none;
   }
 }
+
 .solicitation-card {
   min-width: 85vw;
 }
+
 .btn-action {
   min-width: 90px;
 }
+
 .flex-1 {
   flex: 1;
   min-width: 0;

+ 213 - 104
src/components/dashboard/NextSchedulesDetailsDialog.vue

@@ -1,205 +1,314 @@
 <template>
   <q-dialog ref="dialogRef" @hide="onDialogHide">
-    <q-card class="next-schedule-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-card
+      class="bg-surface next-schedule-dialog-card shadow-card"
+      :flat="false"
+    >
+      <div class="justify-end q-pr-sm q-pt-sm row">
+        <q-btn
+          color="grey-6"
+          dense
+          flat
+          icon="close"
+          round
+          size="sm"
+          @click="onDialogCancel"
+        />
       </div>
 
-      <q-card-section class="column items-center q-pt-xs q-pb-sm">
-        <q-avatar size="72px" class="q-mb-sm">
-          <img v-if="schedule.customer_photo" :src="schedule.customer_photo" style="object-fit:cover" />
-          <span v-else class="full-width full-height flex flex-center" :style="avatarColors[schedule.id % avatarColors.length]" style="font-size:20px;border-radius:50%">{{ getFirstName(schedule.customer_name ?? schedule.client_name)?.slice(0, 2).toUpperCase() ?? '??' }}</span>
+      <q-card-section class="column items-center q-pb-sm q-pt-xs">
+        <q-avatar
+          class="q-mb-sm"
+          size="72px"
+        >
+          <img
+            v-if="schedule.customer_photo"
+            :src="schedule.customer_photo"
+            style="object-fit: cover"
+          />
+
+          <span
+            v-else
+            class="flex flex-center full-height full-width"
+            style="border-radius: 50%; font-size: 20px"
+            :style="avatarColors[schedule.id % avatarColors.length]"
+          >
+            {{
+              getFirstName(schedule.customer_name ?? schedule.client_name)
+                ?.slice(0, 2)
+                .toUpperCase() ?? "??"
+            }}
+          </span>
         </q-avatar>
-        <div class="text-text font16 fontbold">
-          {{ getFirstName(schedule.customer_name ?? schedule.client_name) || '—' }}
+
+        <div class="font16 fontbold text-text">
+          {{ getFirstName(schedule.customer_name ?? schedule.client_name) || "—" }}
+        </div>
+
+        <div class="font16 fontbold q-mt-xs text-price">
+          {{ formatCurrency(schedule.total_amount) }}
+        </div>
+
+        <div class="font12 q-mt-xxs text-grey-6">
+          {{ periodLabel }}
         </div>
-        <div class="text-price font16 fontbold q-mt-xs">{{ formatCurrency(schedule.total_amount) }}</div>
-        <div class="text-grey-6 font12 q-mt-xxs">{{ periodLabel }}</div>
       </q-card-section>
 
       <q-separator class="q-mx-lg" />
 
-      <q-card-section class="column items-center q-py-sm text-text font12">
+      <q-card-section class="column font12 items-center q-py-sm text-text">
         <div class="text-center">
-          <span class="fontbold">{{ weekdayLabel }}</span>
-          <span class="text-grey-7">{{', ' + dayMonthLabel }}</span>
+          <span class="fontbold">
+            {{ weekdayLabel }}
+          </span>
+
+          <span class="text-grey-7">
+            {{ `, ${dayMonthLabel}` }}
+          </span>
         </div>
-        <div class="text-center q-mt-xxs">
-          {{ $t('common.from') }}
-          <span class="fontbold">{{ schedule.start_time?.slice(0, 5) }}</span>
-          {{ $t('common.to') }}
-          <span class="fontbold">{{ schedule.end_time?.slice(0, 5) }}</span>
+
+        <div class="q-mt-xxs text-center">
+          {{ $t("common.from") }}
+
+          <span class="fontbold">
+            {{ schedule.start_time?.slice(0, 5) }}
+          </span>
+
+          {{ $t("common.to") }}
+
+          <span class="fontbold">
+            {{ schedule.end_time?.slice(0, 5) }}
+          </span>
         </div>
       </q-card-section>
 
       <q-separator class="q-mx-lg" />
 
       <q-card-section class="column items-center q-py-sm">
-        <div class="row items-center q-gutter-x-xs">
+        <div class="items-center q-gutter-x-xs row">
           <q-icon
-            :name="schedule.offers_meal ? 'mdi-silverware' : 'mdi-close-circle-outline'"
             :color="schedule.offers_meal ? 'secondary' : 'grey-5'"
+            :name="
+              schedule.offers_meal
+                ? 'mdi-silverware'
+                : 'mdi-close-circle-outline'
+            "
             size="18px"
           />
-          <span class="font12" :class="schedule.offers_meal ? 'text-text' : 'text-grey-6'">
-            {{ schedule.offers_meal
-              ? $t('provider.dashboard.next_schedules.offers_meal')
-              : $t('provider.dashboard.next_schedules.no_meal') }}
+
+          <span
+            class="font12"
+            :class="schedule.offers_meal ? 'text-text' : 'text-grey-6'"
+          >
+            {{
+              schedule.offers_meal
+                ? $t("provider.dashboard.next_schedules.offers_meal")
+                : $t("provider.dashboard.next_schedules.no_meal")
+            }}
           </span>
         </div>
       </q-card-section>
 
       <q-separator class="q-mx-lg" />
 
-      <q-card-section class="column items-center q-py-sm q-px-lg">
-        <div class="row items-start q-gutter-x-xs no-wrap">
+      <q-card-section class="column items-center q-px-lg q-py-sm">
+        <div class="items-start no-wrap q-gutter-x-xs row">
           <div class="col-1 q-my-auto">
-            <q-icon name="mdi-map-marker-outline" color="primary" size="20px" class="q-mt-xxs flex-shrink-0" />
+            <q-icon
+              class="flex-shrink-0 q-mt-xxs"
+              color="primary"
+              name="mdi-map-marker-outline"
+              size="20px"
+            />
           </div>
+
           <div class="col">
-            <span class="text-grey-8 text-center font12">{{ formattedAddress || 'N/A' }}</span>
+            <span class="font12 text-center text-grey-8">
+              {{ formattedAddress || "N/A" }}
+            </span>
           </div>
         </div>
-        <div class="text-grey-6 text-center font12 q-mt-xs">
-          {{ $t('provider.dashboard.solicitations.distance_prefix') }}
-          <strong>{{ (schedule.distance_km ?? 0) + ' km' }}</strong>
-          {{ $t('provider.dashboard.solicitations.distance_suffix') }}
+
+        <div class="font12 q-mt-xs text-center text-grey-6">
+          {{ $t("provider.dashboard.solicitations.distance_prefix") }}
+
+          <strong>
+            {{ `${schedule.distance_km ?? 0} km` }}
+          </strong>
+
+          {{ $t("provider.dashboard.solicitations.distance_suffix") }}
         </div>
+
         <q-btn
+          class="q-mt-xs"
+          color="primary"
+          dense
           flat
+          icon="mdi-content-copy"
           no-caps
-          dense
-          color="primary"
           size="sm"
-          icon="mdi-content-copy"
           :label="$t('provider.dashboard.next_schedules.copy_address')"
-          class="q-mt-xs"
           @click="copyAddress"
         />
       </q-card-section>
 
-      <q-card-section class="q-pt-xs q-pb-sm q-px-lg">
+      <q-card-section class="q-pb-sm q-pt-xs q-px-lg">
         <q-btn
-          unelevated
-          rounded
-          no-caps
+          class="btn-close font14 fontbold full-width"
           color="primary"
+          no-caps
+          rounded
           size="sm"
-          class="full-width btn-close font14 fontbold"
+          unelevated
           :label="$t('provider.dashboard.cancel_schedule.close')"
           @click="onDialogCancel"
         />
       </q-card-section>
 
-      <q-card-section class="q-pt-none q-pb-md text-center">
-        <div class="row justify-center q-gutter-x-lg">
+      <q-card-section class="q-pb-md q-pt-none text-center">
+        <div class="justify-center q-gutter-x-lg row">
           <q-btn
+            color="grey-7"
             flat
             no-caps
-            color="grey-7"
             size="sm"
             :label="$t('provider.dashboard.next_schedules.btn_cancel_service')"
             @click="openCancelDialog"
           />
+
           <q-btn
+            color="grey-7"
             flat
             no-caps
-            color="grey-7"
             size="sm"
             :label="$t('provider.dashboard.next_schedules.btn_need_help')"
             @click="openHelp"
           />
         </div>
       </q-card-section>
-
     </q-card>
   </q-dialog>
 </template>
 
 <script setup>
-import { computed } from 'vue'
-import { useDialogPluginComponent, useQuasar } from 'quasar'
-import { useI18n } from 'vue-i18n'
-import { formatCurrency, formatAddress, getFirstName } from 'src/helpers/utils'
-import { labelsPeriodTypes } from 'src/helpers/arraysOptions/labelsPeriodTypes.js'
-import ScheduleCancelDialog from './ScheduleCancelDialog.vue'
-import ProfileHelpDialog from 'src/components/profile/ProfileHelpDialog.vue'
+import { computed } from "vue";
+import { formatAddress, formatCurrency, getFirstName } from "src/helpers/utils";
+import { labelsPeriodTypes } from "src/helpers/labelsPeriodTypes.js";
+import { useDialogPluginComponent, useQuasar } from "quasar";
+import { useI18n } from "vue-i18n";
 
-const avatarColors = [
-  { background: '#ffd5df', color: '#932e57' },
-  { background: '#d7e8ff', color: '#2158a8' },
-  { background: '#dfd',    color: '#2a7a3b' },
-  { background: '#ffe5cc', color: '#8a4500' },
-];
+import ProfileHelpDialog from "src/components/profile/ProfileHelpDialog.vue";
+import ScheduleCancelDialog from "./ScheduleCancelDialog.vue";
 
 const props = defineProps({
   schedule: {
+    required: true,
     type: Object,
-    required: true
-  }
-})
+  },
+});
+
+defineEmits([...useDialogPluginComponent.emits]);
+
+const $q = useQuasar();
 
-const { t } = useI18n()
-const $q = useQuasar()
-const { dialogRef, onDialogHide, onDialogCancel, onDialogOK } = useDialogPluginComponent()
+const { dialogRef, onDialogCancel, onDialogHide, onDialogOK } =
+  useDialogPluginComponent();
+
+const { t } = useI18n();
+
+const dayMonthLabel = computed(() => {
+  const date = parseLocalDate(props.schedule.date);
+
+  if (!date) return "";
+
+  return date.toLocaleDateString("pt-BR", {
+    day: "2-digit",
+    month: "2-digit",
+  });
+});
+
+const formattedAddress = computed(() => formatAddress(props.schedule.address));
 
 const periodLabel = computed(() => {
-  const found = labelsPeriodTypes.find(l => l.value == props.schedule.period_type)
-  return found ? t(found.label) : ''
-})
-
-const parseLocalDate = (dateStr) => {
-  if (!dateStr) return null
-  const s = String(dateStr)
-  const iso = s.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})/)
-  if (dmy) return new Date(+dmy[3], +dmy[2] - 1, +dmy[1])
-  return null
-}
+  const period = labelsPeriodTypes.find(
+    (label) => label.value == props.schedule.period_type,
+  );
+
+  return period ? t(period.label) : "";
+});
 
 const weekdayLabel = computed(() => {
-  const d = parseLocalDate(props.schedule.date)
-  if (!d) return ''
-  const w = d.toLocaleDateString('pt-BR', { weekday: 'long' })
-  return w.charAt(0).toUpperCase() + w.slice(1)
-})
+  const date = parseLocalDate(props.schedule.date);
 
-const dayMonthLabel = computed(() => {
-  const d = parseLocalDate(props.schedule.date)
-  if (!d) return ''
-  return d.toLocaleDateString('pt-BR', { day: '2-digit', month: '2-digit' })
-})
+  if (!date) return "";
+
+  const weekday = date.toLocaleDateString("pt-BR", { weekday: "long" });
+
+  return weekday.charAt(0).toUpperCase() + weekday.slice(1);
+});
 
-const formattedAddress = computed(() => formatAddress(props.schedule.address))
+const avatarColors = [
+  { background: "#ffd5df", color: "#932e57" },
+  { background: "#d7e8ff", color: "#2158a8" },
+  { background: "#dfd",    color: "#2a7a3b" },
+  { background: "#ffe5cc", color: "#8a4500" },
+];
 
 const copyAddress = () => {
-  const addr = formattedAddress.value
-  if (addr) navigator.clipboard.writeText(addr)
-  $q.notify({ message: t('provider.dashboard.next_schedules.address_copied'), color: 'positive' })
-}
+  const address = formattedAddress.value;
+
+  if (address) {
+    navigator.clipboard.writeText(address);
+  }
+
+  $q.notify({
+    color: "positive",
+    message: t("provider.dashboard.next_schedules.address_copied"),
+  });
+};
+
+const parseLocalDate = (dateString) => {
+  if (!dateString) return null;
+
+  const date = String(dateString);
+
+  const isoDate = date.match(/^(\d{4})-(\d{2})-(\d{2})/);
+
+  if (isoDate) {
+    return new Date(+isoDate[1], +isoDate[2] - 1, +isoDate[3]);
+  }
+
+  const brDate = date.match(/^(\d{2})\/(\d{2})\/(\d{4})/);
+
+  if (brDate) {
+    return new Date(+brDate[3], +brDate[2] - 1, +brDate[1]);
+  }
+
+  return null;
+};
+
+//
 
 const openCancelDialog = () => {
   $q.dialog({
     component: ScheduleCancelDialog,
-    componentProps: { schedule: props.schedule }
+    componentProps: { schedule: props.schedule },
   }).onOk(() => {
-    onDialogOK({ action: 'cancelled', id: props.schedule.id })
-  })
-}
+    onDialogOK({ action: "cancelled", id: props.schedule.id });
+  });
+};
 
 const openHelp = () => {
-  $q.dialog({ component: ProfileHelpDialog })
-}
+  $q.dialog({ component: ProfileHelpDialog });
+};
 </script>
 
-<style scoped lang="scss">
+<style lang="scss" scoped>
 .next-schedule-dialog-card {
-  width: 320px;
-  max-width: 92vw;
   border-radius: 20px !important;
+  max-width: 92vw;
   overflow: hidden;
+  width: 320px;
 }
 
 .text-price {

+ 400 - 184
src/components/dashboard/SolicitationDetailsDialog.vue

@@ -1,26 +1,58 @@
 <template>
-  <q-dialog ref="dialogRef" @hide="onDialogHide">
-    <q-card class="solicitation-dialog-card bg-surface shadow-card" :flat="false">
-
-      <div class="text-text">
-      </div>
+  <q-dialog
+    ref="dialogRef"
+    @hide="onDialogHide"
+  >
+    <q-card
+      class="solicitation-dialog-card bg-surface shadow-card"
+      :flat="false"
+    >
       <template v-if="isPaid">
         <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>
 
         <q-card-section class="column items-center q-pt-xs q-pb-xs">
-          <q-avatar size="72px" class="q-mb-sm">
-            <img v-if="solicitation.customer_photo" :src="solicitation.customer_photo" style="object-fit:cover" />
-            <span v-else class="full-width full-height flex flex-center" :style="avatarColors[solicitation.id % avatarColors.length]" style="font-size:20px;border-radius:50%">{{ solicitation.client_name?.slice(0,2).toUpperCase() ?? '??' }}</span>
+          <q-avatar class="q-mb-sm" size="72px">
+            <img
+              v-if="solicitation.customer_photo"
+              :src="solicitation.customer_photo"
+              style="object-fit: cover"
+            />
+
+            <span
+              v-else
+              class="full-width full-height flex flex-center"
+              style="font-size: 20px; border-radius: 50%"
+              :style="avatarColors[solicitation.id % avatarColors.length]"
+            >
+              {{ solicitation.client_name?.slice(0, 2).toUpperCase() ?? "??" }}
+            </span>
           </q-avatar>
+
           <div class="text-text font16 fontbold">
             {{ solicitation.client_name }}
           </div>
-          <div v-if="solicitation.address?.district" class="text-grey-6 font12 q-mt-xs">
+
+          <div
+            v-if="solicitation.address?.district"
+            class="text-grey-6 font12 q-mt-xs"
+          >
             {{ solicitation.address.district }}
           </div>
-          <div v-if="solicitation.address" class="text-grey-6 font12 text-center q-mt-xs">
+
+          <div
+            v-if="solicitation.address"
+            class="text-grey-6 font12 text-center q-mt-xs"
+          >
             {{ formatAddressShort(solicitation.address) }}
           </div>
         </q-card-section>
@@ -34,11 +66,14 @@
             class="row items-center q-gutter-x-sm q-mb-xs"
           >
             <q-icon
-              :name="service.active ? 'mdi-check' : 'mdi-close'"
               :color="service.active ? 'secondary' : 'grey-4'"
+              :name="service.active ? 'mdi-check' : 'mdi-close'"
               size="16px"
             />
-            <span class="text-grey-8 font14">{{ service.label }}</span>
+
+            <span class="text-grey-8 font14">
+              {{ service.label }}
+            </span>
           </div>
         </q-card-section>
 
@@ -46,57 +81,86 @@
 
         <q-card-section class="q-pt-xs q-pb-xs q-px-lg">
           <div class="detail-row">
-            <span class="text-secondary font12 fontsemibold">{{ $t('common.terms.date') }}</span>
-            <span class="text-grey-9 font13">{{ fullDateLabel }}</span>
+            <span class="text-secondary font12 fontsemibold">
+              {{ $t("common.terms.date") }}
+            </span>
+
+            <span class="text-grey-9 font13">
+              {{ fullDateLabel }}
+            </span>
           </div>
+
           <div class="detail-row">
-            <span class="text-secondary font12 fontsemibold">{{ $t('common.terms.hour') }}</span>
+            <span class="text-secondary font12 fontsemibold">
+              {{ $t("common.terms.hour") }}
+            </span>
+
             <span class="text-grey-9 font13">
-              {{ solicitation.start_time?.slice(0, 5) }} {{ $t('common.to') }} {{ solicitation.end_time?.slice(0, 5) }}
+              {{ solicitation.start_time?.slice(0, 5) }} {{ $t("common.to") }}
+              {{ solicitation.end_time?.slice(0, 5) }}
             </span>
           </div>
+
           <div class="detail-row">
-            <span class="text-secondary font12 fontsemibold">{{ $t('common.terms.total_amount') }}</span>
-            <span class="text-grey-9 font13">{{ formatCurrency(baseAmount) }}</span>
+            <span class="text-secondary font12 fontsemibold">
+              {{ $t("common.terms.total_amount") }}
+            </span>
+
+            <span class="text-grey-9 font13">
+              {{ formatCurrency(baseAmount) }}
+            </span>
           </div>
+
           <div class="detail-row">
-            <span class="text-secondary font12 fontsemibold">{{ $t('provider.dashboard.cancel_schedule.service_fee') }}</span>
-            <span class="text-grey-9 font13">{{ formatCurrency(serviceFee) }}</span>
+            <span class="text-secondary font12 fontsemibold">
+              {{ $t("provider.dashboard.cancel_schedule.service_fee") }}
+            </span>
+
+            <span class="text-grey-9 font13">
+              {{ formatCurrency(serviceFee) }}
+            </span>
           </div>
         </q-card-section>
 
         <q-separator class="q-mx-md q-my-xs" />
 
         <q-card-section class="q-pt-xs q-pb-sm text-center">
-          <span class="text-grey-9 fontbold">{{ $t('provider.dashboard.cancel_schedule.total') }}</span>
-          <span class="text-secondary fontbold font22 q-ml-xs">{{ formatCurrency(solicitation.total_amount) }}</span>
+          <span class="text-grey-9 fontbold">
+            {{ $t("provider.dashboard.cancel_schedule.total") }}
+          </span>
+
+          <span class="text-secondary fontbold font22 q-ml-xs">
+            {{ formatCurrency(paidTotalAmount) }}
+          </span>
         </q-card-section>
 
         <q-card-section class="q-pt-none q-pb-xs q-px-lg">
           <q-btn
-            unelevated
-            rounded
-            no-caps
-            color="secondary"
             class="full-width btn-close-paid font16 fontbold"
+            color="secondary"
+            no-caps
+            rounded
+            unelevated
             :label="$t('provider.dashboard.cancel_schedule.close')"
             @click="onDialogCancel"
           />
         </q-card-section>
+
         <q-card-section class="q-pt-xs q-pb-md text-center">
           <div class="row justify-center q-gutter-x-lg">
             <q-btn
+              color="grey-7"
               flat
               no-caps
-              color="grey-7"
               size="sm"
               :label="$t('provider.dashboard.cancel_schedule.btn_cancel_order')"
               @click="openCancelDialog"
             />
+
             <q-btn
+              color="grey-7"
               flat
               no-caps
-              color="grey-7"
               size="sm"
               :label="$t('provider.dashboard.cancel_schedule.help')"
             />
@@ -106,122 +170,205 @@
 
       <template v-else-if="view === 'details'">
         <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>
 
         <q-card-section class="column items-center q-pt-sm q-pb-sm">
-          <q-avatar size="72px" class="q-mb-sm">
-            <img v-if="solicitation.customer_photo" :src="solicitation.customer_photo" style="object-fit:cover" />
-            <span v-else class="full-width full-height flex flex-center" :style="avatarColors[solicitation.id % avatarColors.length]" style="font-size:20px;border-radius:50%">{{ solicitation.client_name?.slice(0,2).toUpperCase() ?? '??' }}</span>
+          <q-avatar class="q-mb-sm" size="72px">
+            <img
+              v-if="solicitation.customer_photo"
+              :src="solicitation.customer_photo"
+              style="object-fit: cover"
+            />
+
+            <span
+              v-else
+              class="full-width full-height flex flex-center"
+              style="font-size: 20px; border-radius: 50%"
+              :style="avatarColors[solicitation.id % avatarColors.length]"
+            >
+              {{ solicitation.client_name?.slice(0, 2).toUpperCase() ?? "??" }}
+            </span>
           </q-avatar>
 
-          <div class="text-text font16 fontbold">{{ solicitation.client_name }}</div>
+          <div class="text-text font16 fontbold">
+            {{ solicitation.client_name }}
+          </div>
+
+          <div class="text-price text-primary font16 fontbold q-mt-xs">
+            {{ formatCurrency(solicitation.total_amount) }}
+          </div>
 
-          <div class="text-price text-primary font16 fontbold q-mt-xs">{{ formatCurrency(solicitation.total_amount) }}</div>
-          <div class="text-grey-6 font12">{{ periodLabel }}</div>
+          <div class="text-grey-6 font12">
+            {{ periodLabel }}
+          </div>
         </q-card-section>
 
-        <q-card-section class="column items-center q-pt-none q-pb-sm text-text font14">
+        <q-card-section
+          class="column items-center q-pt-none q-pb-sm text-text font14"
+        >
           <div class="text-center">
-            <span class="fontbold">{{ weekdayLabel + ' ,' + dayMonthLabel }}</span>
+            <span class="fontbold">
+              {{ weekdayLabel + " ," + dayMonthLabel }}
+            </span>
           </div>
+
           <div class="text-center">
-            {{ $t('common.from') }} <strong>{{ solicitation.start_time?.slice(0, 5) }}</strong>
-            {{ $t('common.to') }} <strong>{{ solicitation.end_time?.slice(0, 5) }}</strong>
+            {{ $t("common.from") }}
+
+            <strong>
+              {{ solicitation.start_time?.slice(0, 5) }}
+            </strong>
+
+            {{ $t("common.to") }}
+
+            <strong>
+              {{ solicitation.end_time?.slice(0, 5) }}
+            </strong>
           </div>
         </q-card-section>
 
         <q-card-section class="column items-center q-pt-none q-pb-xs">
           <div class="row items-center q-gutter-x-xs">
             <q-icon
-              :name="solicitation.offers_meal ? 'mdi-food' : 'mdi-food-off'"
               :color="solicitation.offers_meal ? 'positive' : 'grey-5'"
+              :name="solicitation.offers_meal ? 'mdi-food' : 'mdi-food-off'"
               size="18px"
             />
+
             <span class="text-grey-7 font14">
-              {{ solicitation.offers_meal
-                ? $t('provider.dashboard.solicitations.offers_meal')
-                : $t('provider.dashboard.solicitations.not_offers_meal') }}
+              {{
+                solicitation.offers_meal
+                  ? $t("provider.dashboard.solicitations.offers_meal")
+                  : $t("provider.dashboard.solicitations.not_offers_meal")
+              }}
             </span>
           </div>
         </q-card-section>
 
         <q-card-section class="column items-center q-pt-xs q-pb-sm text-text">
           <div class="row items-center q-gutter-x-xs">
-            <q-icon name="mdi-map-marker" color="primary" size="20px" />
-            <span class="font16 fontbold">{{ solicitation.address?.district || 'N/A' }}</span>
+            <q-icon
+              color="primary"
+              name="mdi-map-marker"
+              size="20px"
+            />
+
+            <span class="font16 fontbold">
+              {{ solicitation.address?.district || "N/A" }}
+            </span>
           </div>
+
           <div class="text-grey-6 text-center font12 q-mt-xs">
-            {{ $t('provider.dashboard.solicitations.distance_prefix') }}
-            <strong>{{ (solicitation.distance_km ?? 0) + ' km' }}</strong>
-            {{ $t('provider.dashboard.solicitations.distance_suffix') }}
+            {{ $t("provider.dashboard.solicitations.distance_prefix") }}
+
+            <strong>
+              {{ (solicitation.distance_km ?? 0) + " km" }}
+            </strong>
+
+            {{ $t("provider.dashboard.solicitations.distance_suffix") }}
           </div>
+
           <div class="q-mt-xs font12">
-            <span class="text-grey-6">{{ $t('provider.dashboard.solicitations.via_agenda_text') }} </span>
-            <span class="text-primary fontbold">{{ ' ' + $t('provider.dashboard.solicitations.via_agenda') }}</span>
+            <span class="text-grey-6">
+              {{ $t("provider.dashboard.solicitations.via_agenda_text") }}
+            </span>
+
+            <span class="text-primary fontbold">
+              {{ " " + $t("provider.dashboard.solicitations.via_agenda") }}
+            </span>
           </div>
         </q-card-section>
 
         <q-separator />
 
-        <q-card-section v-if="solicitation?.status != 'accepted'" class="q-py-sm">
+        <q-card-section
+          v-if="solicitation?.status != 'accepted'"
+          class="q-py-sm"
+        >
           <div class="row justify-center q-gutter-x-md">
             <q-btn
-              unelevated
-              rounded
-              no-caps
               class="btn-action bg-grey-3 text-grey-8"
+              no-caps
+              rounded
+              unelevated
               :label="$t('common.refuse')"
               @click="view = 'confirm-reject'"
             />
+
             <q-btn
-              unelevated
-              rounded
-              no-caps
-              color="secondary"
               class="btn-action"
+              color="secondary"
+              no-caps
+              rounded
+              unelevated
               :label="$t('common.accept')"
               @click="view = 'confirm-accept'"
             />
           </div>
+
           <div class="row justify-center q-mt-xs">
-            <q-btn flat no-caps color="grey-7" size="sm" :label="$t('common.actions.back')" @click="onDialogCancel" />
+            <q-btn
+              color="grey-7"
+              flat
+              no-caps
+              size="sm"
+              :label="$t('common.actions.back')"
+              @click="onDialogCancel"
+            />
           </div>
         </q-card-section>
 
         <div class="q-pa-md font12 text-primary">
-          <span>{{ $t('provider.dashboard.solicitations.same_day_warning_before') }}</span>
-          <span class="fontbold">{{ $t('provider.dashboard.solicitations.same_day_warning_bold') }}</span>
-          <span>{{ $t('provider.dashboard.solicitations.same_day_warning_after') }}</span>
+          <span>
+            {{ $t("provider.dashboard.solicitations.same_day_warning_before") }}
+          </span>
+
+          <span class="fontbold">
+            {{ $t("provider.dashboard.solicitations.same_day_warning_bold") }}
+          </span>
+
+          <span>
+            {{ $t("provider.dashboard.solicitations.same_day_warning_after") }}
+          </span>
         </div>
       </template>
 
       <template v-else-if="view === 'confirm-accept'">
         <q-card-section class="column items-center q-pt-xl q-pb-lg text-center">
           <div class="confirm-title text-secondary fontbold">
-            {{ $t('provider.dashboard.solicitations.confirm_accept') }}
+            {{ $t("provider.dashboard.solicitations.confirm_accept") }}
           </div>
         </q-card-section>
 
         <q-card-section class="q-pt-none q-pb-xl">
           <div class="row justify-center q-gutter-x-md">
             <q-btn
-              unelevated
-              rounded
-              no-caps
-              color="purple-5"
               class="btn-action"
+              color="purple-5"
+              no-caps
+              rounded
+              unelevated
               :label="$t('common.actions.back')"
               @click="backFromConfirm"
             />
+
             <q-btn
-              unelevated
-              rounded
-              no-caps
-              color="secondary"
               class="btn-action"
-              :loading="loading"
+              color="secondary"
+              no-caps
+              rounded
+              unelevated
               :label="$t('common.accept')"
+              :loading="loading"
               @click="confirmAccept"
             />
           </div>
@@ -231,31 +378,36 @@
       <template v-else-if="view === 'confirm-reject'">
         <q-card-section class="column items-center q-pt-xl q-pb-md text-center">
           <div class="confirm-title text-secondary fontbold">
-            {{ $t('provider.dashboard.solicitations.confirm_reject') }}
+            {{ $t("provider.dashboard.solicitations.confirm_reject") }}
           </div>
+
           <div class="q-mt-md text-grey-7 confirm-tip font13">
-            <strong>{{ $t('provider.dashboard.solicitations.tip_label') }} </strong>
-            {{ $t('provider.dashboard.solicitations.reject_tip') }}
+            <strong>
+              {{ $t("provider.dashboard.solicitations.tip_label") }}
+            </strong>
+  
+            {{ $t("provider.dashboard.solicitations.reject_tip") }}
           </div>
         </q-card-section>
 
         <q-card-section class="q-pt-none q-pb-xl">
           <div class="row justify-center q-gutter-x-md">
             <q-btn
-              unelevated
-              rounded
-              no-caps
-              color="purple-5"
               class="btn-action"
+              color="purple-5"
+              no-caps
+              rounded
+              unelevated
               :label="$t('common.actions.back')"
               @click="backFromConfirm"
             />
+  
             <q-btn
-              unelevated
-              rounded
-              no-caps
-              color="secondary"
               class="btn-action"
+              color="secondary"
+              no-caps
+              rounded
+              unelevated
               :loading="loading"
               :label="$t('common.refuse')"
               @click="confirmReject"
@@ -263,146 +415,211 @@
           </div>
         </q-card-section>
       </template>
-
     </q-card>
   </q-dialog>
 </template>
 
 <script setup>
-import { ref, computed } from 'vue'
-import { useDialogPluginComponent, useQuasar } from 'quasar'
-import { useI18n } from 'vue-i18n'
-import { formatCurrency } from 'src/helpers/utils'
-import { labelsPeriodTypes } from 'src/helpers/arraysOptions/labelsPeriodTypes.js'
-import ScheduleCancelDialog from './ScheduleCancelDialog.vue'
+import { computed, ref } from "vue";
+import { formatCurrency } from "src/helpers/utils";
+import { labelsPeriodTypes } from "src/helpers/labelsPeriodTypes.js";
+import { useDialogPluginComponent, useQuasar } from "quasar";
+import { useI18n } from "vue-i18n";
 
-const avatarColors = [
-  { background: '#ffd5df', color: '#932e57' },
-  { background: '#d7e8ff', color: '#2158a8' },
-  { background: '#dfd',    color: '#2a7a3b' },
-  { background: '#ffe5cc', color: '#8a4500' },
-];
+import ScheduleCancelDialog from "./ScheduleCancelDialog.vue";
 
 const props = defineProps({
+  initialView: {
+    default: "details",
+    type: String,
+    validator: (v) =>
+      ["details", "confirm-accept", "confirm-reject"].includes(v),
+  },
   solicitation: {
+    required: true,
     type: Object,
-    required: true
   },
-  initialView: {
-    type: String,
-    default: 'details',
-    validator: (v) => ['details', 'confirm-accept', 'confirm-reject'].includes(v)
-  }
-})
+});
 
-const { t } = useI18n()
-const $q = useQuasar()
-const { dialogRef, onDialogHide, onDialogCancel, onDialogOK } = useDialogPluginComponent()
+defineEmits([...useDialogPluginComponent.emits]);
 
-const isPaid = computed(() => props.solicitation.status === 'paid')
+const loading = ref(false);
+const view = ref(props.initialView);
 
-const view = ref(props.initialView)
-const loading = ref(false)
+const $q = useQuasar();
 
-const periodLabel = computed(() => {
-  const found = labelsPeriodTypes.find(l => l.value == props.solicitation.period_type)
-  return found ? t(found.label) : ''
-})
-
-const parseLocalDate = (dateStr) => {
-  if (!dateStr) return null
-  const s = String(dateStr)
-  const iso = s.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})/)
-  if (dmy) return new Date(+dmy[3], +dmy[2] - 1, +dmy[1])
-  return null
-}
+const { dialogRef, onDialogCancel, onDialogHide, onDialogOK } =
+  useDialogPluginComponent();
 
-const weekdayLabel = computed(() => {
-  const d = parseLocalDate(props.solicitation.date)
-  if (!d) return ''
-  const w = d.toLocaleDateString('pt-BR', { weekday: 'long' })
-  return w.charAt(0).toUpperCase() + w.slice(1)
-})
+const { t } = useI18n();
+
+const baseAmount = computed(() => {
+  const amount =
+    props.solicitation.service_amount ?? props.solicitation.base_amount;
+
+  return parseAmount(amount);
+});
 
 const dayMonthLabel = computed(() => {
-  const d = parseLocalDate(props.solicitation.date)
-  if (!d) return ''
-  return d.toLocaleDateString('pt-BR', { day: '2-digit', month: '2-digit' })
-})
+  const date = parseLocalDate(props.solicitation.date);
+
+  if (!date) return "";
+
+  return date.toLocaleDateString("pt-BR", {
+    day: "2-digit",
+    month: "2-digit",
+  });
+});
 
 const fullDateLabel = computed(() => {
-  const d = parseLocalDate(props.solicitation.date)
-  if (!d) return ''
-  return d.toLocaleDateString('pt-BR', { day: '2-digit', month: 'long', year: 'numeric' })
-})
+  const date = parseLocalDate(props.solicitation.date);
+
+  if (!date) return "";
 
-const SERVICE_FEE_RATE = 0.11
+  return date.toLocaleDateString("pt-BR", {
+    day: "2-digit",
+    month: "long",
+    year: "numeric",
+  });
+});
+
+const isPaid = computed(() => props.solicitation.status === "paid");
+
+const paidTotalAmount = computed(() =>
+  parseAmount(
+    props.solicitation.gross_amount ?? props.solicitation.total_amount,
+  ),
+);
+
+const periodLabel = computed(() => {
+  const period = labelsPeriodTypes.find(
+    (label) => label.value == props.solicitation.period_type,
+  );
+
+  return period ? t(period.label) : "";
+});
 
 const serviceFee = computed(() => {
-  const total = parseFloat(props.solicitation.total_amount) || 0
-  return parseFloat((total * SERVICE_FEE_RATE).toFixed(2))
-})
-const baseAmount = computed(() => {
-  const total = parseFloat(props.solicitation.total_amount) || 0
-  return parseFloat((total - serviceFee.value).toFixed(2))
-})
+  const fee =
+    props.solicitation.platform_fee_amount ??
+    props.solicitation.service_fee_amount ??
+    props.solicitation.service_fee;
+
+  return parseAmount(fee);
+});
 
 const servicesList = computed(() => {
-  const list = []
-  const s = props.solicitation
-  if (s.schedule_type === 'custom' && s.specialities?.length) {
-    s.specialities.forEach(sp => list.push({ label: sp.name, active: true }))
+  const services = [];
+  const solicitation = props.solicitation;
+
+  if (
+    solicitation.schedule_type === "custom" &&
+    solicitation.specialities?.length
+  ) {
+    solicitation.specialities.forEach((speciality) => {
+      services.push({ active: true, label: speciality.name });
+    });
   }
-  if (s.offers_meal !== undefined) {
-    list.push({
-      label: s.offers_meal
-        ? t('provider.dashboard.solicitations.offers_meal')
-        : t('provider.dashboard.solicitations.not_offers_meal'),
-      active: !!s.offers_meal
-    })
+
+  if (solicitation.offers_meal !== undefined) {
+    services.push({
+      active: !!solicitation.offers_meal,
+      label: solicitation.offers_meal
+        ? t("provider.dashboard.solicitations.offers_meal")
+        : t("provider.dashboard.solicitations.not_offers_meal"),
+    });
   }
-  if (!list.length) {
-    list.push({ label: t('provider.dashboard.next_schedules.default'), active: true })
+
+  if (!services.length) {
+    services.push({
+      active: true,
+      label: t("provider.dashboard.next_schedules.default"),
+    });
   }
-  return list
-})
-
-const formatAddressShort = (addr) => {
-  if (!addr) return ''
-  const parts = [addr.address, addr.number].filter(Boolean)
-  const street = parts.join(', ')
-  const suffix = addr.district ? `, ${addr.district}` : ''
-  return street + suffix
-}
+
+  return services;
+});
+
+const weekdayLabel = computed(() => {
+  const date = parseLocalDate(props.solicitation.date);
+
+  if (!date) return "";
+
+  const weekday = date.toLocaleDateString("pt-BR", { weekday: "long" });
+
+  return weekday.charAt(0).toUpperCase() + weekday.slice(1);
+});
+
+const avatarColors = [
+  { background: "#ffd5df", color: "#932e57" },
+  { background: "#d7e8ff", color: "#2158a8" },
+  { background: "#dfd",    color: "#2a7a3b" },
+  { background: "#ffe5cc", color: "#8a4500" },
+];
 
 const backFromConfirm = () => {
-  if (props.initialView !== 'details') {
-    onDialogCancel()
+  if (props.initialView !== "details") {
+    onDialogCancel();
   } else {
-    view.value = 'details'
+    view.value = "details";
   }
-}
+};
 
 const confirmAccept = () => {
-  loading.value = true
-  onDialogOK({ action: 'accept', id: props.solicitation.id })
-}
+  loading.value = true;
+
+  onDialogOK({ action: "accept", id: props.solicitation.id });
+};
 
 const confirmReject = () => {
-  loading.value = true
-  onDialogOK({ action: 'reject', id: props.solicitation.id })
-}
+  loading.value = true;
+
+  onDialogOK({ action: "reject", id: props.solicitation.id });
+};
+
+const formatAddressShort = (address) => {
+  if (!address) return "";
+
+  const street = [address.address, address.number].filter(Boolean).join(", ");
+  const district = address.district ? `, ${address.district}` : "";
+
+  return street + district;
+};
 
 const openCancelDialog = () => {
   $q.dialog({
     component: ScheduleCancelDialog,
-    componentProps: { schedule: props.solicitation }
+    componentProps: { schedule: props.solicitation },
   }).onOk(() => {
-    onDialogOK({ action: 'cancelled', id: props.solicitation.id })
-  })
-}
+    onDialogOK({ action: "cancelled", id: props.solicitation.id });
+  });
+};
+
+const parseLocalDate = (dateString) => {
+  if (!dateString) return null;
+
+  const date = String(dateString);
+
+  const isoDate = date.match(/^(\d{4})-(\d{2})-(\d{2})/);
+
+  if (isoDate) {
+    return new Date(+isoDate[1], +isoDate[2] - 1, +isoDate[3]);
+  }
+
+  const brDate = date.match(/^(\d{2})\/(\d{2})\/(\d{4})/);
+
+  if (brDate) {
+    return new Date(+brDate[3], +brDate[2] - 1, +brDate[1]);
+  }
+
+  return null;
+};
+
+const parseAmount = (value) => {
+  const amount = Number.parseFloat(value);
+
+  return Number.isFinite(amount) ? amount : 0;
+};
 </script>
 
 <style scoped lang="scss">
@@ -441,5 +658,4 @@ const openCancelDialog = () => {
   align-items: baseline;
   margin-bottom: 4px;
 }
-
 </style>

+ 42 - 33
src/components/login/LoginStep1Panel.vue

@@ -17,55 +17,64 @@
       rounded
       :bottom-slots="false"
       :placeholder="$t('common.terms.email')"
-      :rules="!phone
+      :rules="!smsEnabled || !phone
         ? [inputRules.requiredHideMessage, inputRules.email]
         : [inputRules.email]"
     />
 
-    <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>
+    <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 font12 fontmedium text-center text-text">
-        {{ $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="bg-grey-4 q-my-sm" />
+        <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 fontlight">
-        {{ ` (${$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"
-      lazy-rules
-      mask="(##) #####-####"
-      no-error-icon
-      :bottom-slots="false"
-      :placeholder="$t('common.terms.phone')"
-      :rules="!email
-        ? [inputRules.requiredHideMessage, validatePhone]
-        : [validatePhone]"
-    />
+      <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, validatePhone]
+          : [validatePhone]"
+      />
+    </template>
   </q-card-section>
 </template>
 
 <script setup>
 import { useInputRules } from 'src/composables/useInputRules';
 
+defineProps({
+  smsEnabled: {
+    type: Boolean,
+    default: false,
+  },
+});
+
 const email = defineModel('email', {
   required: true,
   type: String,

+ 71 - 27
src/components/login/LoginStep3Panel.vue

@@ -122,6 +122,20 @@
       :rules="[inputRules.required, validateBirthDate]"
     />
 
+    <div class="text-text">
+      <span class="font12 fontbold">
+        {{ $t("common.gender") }}
+      </span>
+    </div>
+
+    <q-option-group
+      v-model="form.gender"
+      class="gender-options font12 q-mb-md q-mt-sm text-text"
+      color="primary"
+      type="radio"
+      :options="translatedGenderOptions"
+    />
+
     <div class="text-text">
       <span class="font12 fontbold">
         {{ $t("common.terms.cep") }}
@@ -209,11 +223,13 @@
           class="bg-surface q-mb-md q-mt-sm"
           hide-bottom-space
           input-class="font12 fontmedium text-text"
+          lazy-rules
           no-error-icon
           outlined
           readonly
           rounded
           :placeholder="`${$t('common.terms.district')}...`"
+          :rules="[inputRules.required]"
         />
       </div>
     </div>
@@ -229,11 +245,14 @@
         <q-input
           v-model="form.city"
           class="bg-surface q-mb-md q-mt-sm"
+          hide-bottom-space
           input-class="font12 fontmedium text-text"
+          lazy-rules
           no-error-icon
           outlined
           readonly
           rounded
+          :rules="[inputRules.required]"
         />
       </div>
 
@@ -249,10 +268,12 @@
           class="bg-surface q-mb-md q-mt-sm"
           hide-bottom-space
           input-class="font12 fontmedium text-text"
+          lazy-rules
           no-error-icon
           outlined
           readonly
           rounded
+          :rules="[inputRules.required]"
         />
       </div>
     </div>
@@ -330,13 +351,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) }}
@@ -347,26 +368,36 @@
 
 <script setup>
 import axios from "axios";
-import { useQuasar } from "quasar";
-import LocationMapDialog from "src/components/shared/LocationMapDialog.vue";
+import { computed, ref } from "vue";
+import { genderOptions } from "src/helpers/genderOptions";
 import { useGeolocation } from "src/composables/useGeolocation";
-import { useInputRules } from "src/composables/useInputRules";
-import { ref } from "vue";
 import { useI18n } from "vue-i18n";
+import { useInputRules } from "src/composables/useInputRules";
+import { useQuasar } from "quasar";
+
+import LocationMapDialog from "src/components/shared/LocationMapDialog.vue";
 
 const form = defineModel({
   required: true,
   type: Object,
 });
 
+const { requestPermission, getCurrentPosition } = useGeolocation();
 const { inputRules } = useInputRules();
 const { t } = useI18n();
+
 const $q = useQuasar();
-const { requestPermission, getCurrentPosition } = useGeolocation();
 
 const loadingCep = ref(false);
 const loadingLocation = ref(false);
 
+const translatedGenderOptions = computed(() =>
+  genderOptions.map((option) => ({
+    ...option,
+    label: t(option.label),
+  })),
+);
+
 const addressTypes = [
   {
     icon: "mdi-home-outline",
@@ -394,25 +425,6 @@ const clearCepFields = () => {
   form.value.longitude = null;
 };
 
-const fillAddressFields = (data) => {
-  const hasField = (field) => Object.prototype.hasOwnProperty.call(data, field);
-
-  form.value.address = data.address ?? "";
-  form.value.number = data.number || form.value.number || "";
-  form.value.district = data.district ?? "";
-  form.value.city = data.city ?? "";
-  form.value.state = data.state ?? "";
-  form.value.zip_code = data.zip_code || form.value.zip_code || "";
-
-  if (hasField("lat") || hasField("latitude")) {
-    form.value.latitude = data.lat ?? data.latitude;
-  }
-
-  if (hasField("lng") || hasField("longitude")) {
-    form.value.longitude = data.lng ?? data.longitude;
-  }
-};
-
 const fetchCep = async (rawCep) => {
   const cleaned = rawCep.replace(/\D/g, "");
 
@@ -444,6 +456,25 @@ const fetchCep = async (rawCep) => {
   }
 };
 
+const fillAddressFields = (data) => {
+  const hasField = (field) => Object.prototype.hasOwnProperty.call(data, field);
+
+  form.value.address = data.address ?? "";
+  form.value.number = data.number || form.value.number || "";
+  form.value.district = data.district ?? "";
+  form.value.city = data.city ?? "";
+  form.value.state = data.state ?? "";
+  form.value.zip_code = data.zip_code || form.value.zip_code || "";
+
+  if (hasField("lat") || hasField("latitude")) {
+    form.value.latitude = data.lat ?? data.latitude;
+  }
+
+  if (hasField("lng") || hasField("longitude")) {
+    form.value.longitude = data.lng ?? data.longitude;
+  }
+};
+
 const onCepChange = (value) => {
   const cleaned = value?.replace(/\D/g, "") || "";
 
@@ -515,6 +546,7 @@ const validateBirthDate = (value) => {
   const year = Number(matches[3]);
 
   const date = new Date(year, month - 1, day);
+
   const isValidDate =
     date.getFullYear() === year &&
     date.getMonth() === month - 1 &&
@@ -539,3 +571,15 @@ const validateRG = (value) => {
   );
 };
 </script>
+
+<style lang="scss" scoped>
+.gender-options {
+  :deep(.q-radio) {
+    margin-bottom: 4px;
+  }
+
+  :deep(.q-radio__inner--falsy) {
+    color: rgba(0, 0, 0, 0.62);
+  }
+}
+</style>

+ 94 - 42
src/components/profile/ProfileBankDataDialog.vue

@@ -1,8 +1,21 @@
 <template>
-  <q-dialog ref="dialogRef" maximized persistent transition-hide="slide-right" transition-show="slide-left">
+  <q-dialog
+    ref="dialogRef"
+    maximized
+    persistent
+    transition-hide="slide-right"
+    transition-show="slide-left"
+  >
     <div class="bg-page full-height no-shadow">
       <div class="row items-center q-px-md q-pb-sm bg-white shadow-profile bg-surface dialog-safe-header">
-        <q-btn v-close-popup color="primary" dense flat icon="mdi-chevron-left" round />
+        <q-btn
+          v-close-popup
+          color="primary"
+          dense
+          flat
+          icon="mdi-chevron-left"
+          round
+        />
 
         <q-space />
 
@@ -25,50 +38,69 @@
             {{ $t('profile.bank_data.subtitle') }}
           </div>
 
-          <div class="pix-warning q-pa-md q-mb-md row items-start q-col-gutter-sm">
-            <q-icon class="q-mt-xs" color="warning" name="mdi-alert-outline" size="20px" />
-
-            <div class="col font12">
-              <span class="fontbold">
-                {{ $t('profile.bank_data.warning_title') }}
-              </span>
-
-              {{ $t('profile.bank_data.warning_message') }}
-            </div>
-          </div>
+          <template v-if="pixEnabled">
+            <div class="pix-warning q-pa-md q-mb-md row items-start q-col-gutter-sm">
+              <q-icon
+                class="q-mt-xs"
+                color="warning"
+                name="mdi-alert-outline"
+                size="20px"
+              />
 
-          <q-card class="q-pa-md bg-white shadow-card q-mb-md" style="border-radius: 20px;" :flat="false">
-            <div class="text-center text-text q-mb-sm font14 fontbold">
-              <div>
-                {{ $t('profile.bank_data.pix_title') }}
+              <div class="col font12">
+                <span class="fontbold">
+                  {{ $t('profile.bank_data.warning_title') }}
+                </span>
 
-                <q-badge class="q-ml-xs" color="grey-5" :label="$t('profile.bank_data.in_development')" />
+                {{ $t('profile.bank_data.warning_message') }}
               </div>
             </div>
 
-            <q-input
-              class="q-mb-sm"
-              dense
-              disable
-              input-class="text-text"
-              outlined
-              :placeholder="$t('profile.bank_data.pix_key_placeholder')"
-            />
+            <q-card
+              class="q-pa-md bg-white shadow-card q-mb-md"
+              style="border-radius: 20px;"
+              :flat="false"
+            >
+              <div class="text-center text-text q-mb-sm font14 fontbold">
+                <div>
+                  {{ $t('profile.bank_data.pix_title') }}
+
+                  <q-badge
+                    class="q-ml-xs"
+                    color="grey-5"
+                    :label="$t('profile.bank_data.in_development')"
+                  />
+                </div>
+              </div>
 
-            <q-btn
-              class="full-width"
-              color="grey-4"
-              disable
-              no-caps
-              padding="4px 12px"
-              rounded
-              text-color="grey-7"
-              unelevated
-              :label="$t('profile.bank_data.in_development')"
-            />
-          </q-card>
+              <q-input
+                class="q-mb-sm"
+                dense
+                disable
+                input-class="text-text"
+                outlined
+                :placeholder="$t('profile.bank_data.pix_key_placeholder')"
+              />
 
-          <q-card class="q-pa-md bg-white row shadow-card q-mb-md" style="border-radius: 20px;" :flat="false">
+              <q-btn
+                class="full-width"
+                color="grey-4"
+                disable
+                no-caps
+                padding="4px 12px"
+                rounded
+                text-color="grey-7"
+                unelevated
+                :label="$t('profile.bank_data.in_development')"
+              />
+            </q-card>
+          </template>
+
+          <q-card
+            class="q-pa-md bg-white row shadow-card q-mb-md"
+            style="border-radius: 20px;"
+            :flat="false"
+          >
             <div class="text-center text-text q-mb-sm col-12 font14 fontbold">
               {{ $t('profile.bank_data.bank_account_title') }}
             </div>
@@ -112,7 +144,10 @@
 
               <template #option="scope">
                 <q-item v-bind="scope.itemProps">
-                  <q-item-section class="bank-select-menu__code" side>
+                  <q-item-section
+                    class="bank-select-menu__code"
+                    side
+                  >
                     {{ scope.opt.code }}
                   </q-item-section>
 
@@ -229,6 +264,7 @@
 <script setup>
 import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref } from 'vue';
 import { getBanks } from 'src/api/banks';
+import { getWithdrawalConfiguration } from 'src/api/providerWithdrawal';
 import { updateProviderBankAccount } from 'src/api/providerPaymentMethod';
 import { useDialogPluginComponent, useQuasar } from 'quasar';
 import { useI18n } from 'vue-i18n';
@@ -237,8 +273,8 @@ import { userStore } from 'src/stores/user';
 defineEmits([...useDialogPluginComponent.emits]);
 
 const { dialogRef } = useDialogPluginComponent();
-const $q = useQuasar();
 const { t } = useI18n();
+const $q = useQuasar();
 const user = userStore();
 
 const banks = ref([]);
@@ -247,6 +283,7 @@ const bankOptions = ref([]);
 const bankSelectRef = ref(null);
 const bankSelectWidth = ref(null);
 const loadingBanks = ref(false);
+const pixEnabled = ref(false);
 const savingBank = ref(false);
 
 const bankForm = reactive({
@@ -429,6 +466,8 @@ const saveBank = async () => {
   }
 };
 
+//
+
 const loadBanks = async () => {
   loadingBanks.value = true;
 
@@ -445,6 +484,19 @@ const loadBanks = async () => {
   }
 };
 
+const loadWithdrawalConfiguration = async () => {
+  try {
+    const configuration = await getWithdrawalConfiguration();
+
+    pixEnabled.value = configuration?.pix_enabled === true;
+  } catch (error) {
+    pixEnabled.value = false;
+    console.log(error);
+  }
+};
+
+//
+
 onMounted(async () => {
   const bank = user.user?.provider?.recipient_default_bank_account;
 
@@ -458,7 +510,7 @@ onMounted(async () => {
     bankAlreadyRegistered.value = true;
   }
 
-  await loadBanks();
+  await Promise.all([loadBanks(), loadWithdrawalConfiguration()]);
 
   await updateBankSelectWidth();
 

+ 10 - 0
src/helpers/genderOptions.js

@@ -0,0 +1,10 @@
+export const genderOptions = [
+  { label: "common.gender_options.female", value: "female" },
+  { label: "common.gender_options.male",   value: "male" },
+  { label: "common.gender_options.other",  value: "other" },
+
+  {
+    label: "common.gender_options.prefer_not_to_say",
+    value: "prefer_not_to_say",
+  },
+];

+ 0 - 0
src/helpers/arraysOptions/labelsPeriodTypes.js → src/helpers/labelsPeriodTypes.js


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

@@ -1,5 +1,12 @@
 {
   "common": {
+    "gender": "Gender",
+    "gender_options": {
+      "female": "Female",
+      "male": "Male",
+      "other": "Other",
+      "prefer_not_to_say": "Prefer not to say"
+    },
     "see_all": "See all",
     "details": "View details",
     "accept": "Accept",
@@ -171,6 +178,7 @@
           "full_name": "Full Name",
           "birth_date": "Date of birth",
           "birth_date_placeholder": "mm/dd/yyyy",
+          "gender_required": "Select a gender option.",
           "no_complement": "Address without complement",
           "complement_placeholder": "Ex: Apartment, Suite, House.",
           "address_placeholder": "Street, Number, Neighborhood, City - ST",

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

@@ -1,5 +1,12 @@
 {
   "common": {
+    "gender": "Género",
+    "gender_options": {
+      "female": "Femenino",
+      "male": "Masculino",
+      "other": "Otro",
+      "prefer_not_to_say": "Prefiero no informar"
+    },
     "see_all": "ver todo",
     "details": "ver detalles",
     "accept": "aceptar",
@@ -171,6 +178,7 @@
           "full_name": "Nombre Completo",
           "birth_date": "Fecha de nacimiento",
           "birth_date_placeholder": "dd/mm/aaaa",
+          "gender_required": "Seleccione una opción de género.",
           "no_complement": "Dirección sin complemento",
           "complement_placeholder": "Ej: Apartamento, Conjunto, Casa.",
           "address_placeholder": "Calle, Número, Barrio, Ciudad - UF",

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

@@ -1,5 +1,12 @@
 {
   "common": {
+    "gender": "Gênero",
+    "gender_options": {
+      "female": "Feminino",
+      "male": "Masculino",
+      "other": "Outro",
+      "prefer_not_to_say": "Prefiro não informar"
+    },
     "see_all": "ver todas",
     "details": "ver detalhes",
     "accept": "aceitar",
@@ -171,6 +178,7 @@
           "full_name": "Nome Completo",
           "birth_date": "Data de nascimento",
           "birth_date_placeholder": "dd/mm/aaaa",
+          "gender_required": "Selecione uma opção de gênero.",
           "no_complement": "Endereço sem complemento",
           "complement_placeholder": "Ex: Apartamento, Conjunto, Casa.",
           "address_placeholder": "Rua, Número, Bairro, Cidade - UF",

+ 49 - 2
src/pages/LoginPage.vue

@@ -58,12 +58,17 @@
         >
           <div
             class="flow-content"
-            :class="{ 'flow-content--centered': steps <= 2 && !showSubStep }"
+            :class="{
+              'flow-content--centered': steps <= 2 && !showSubStep,
+              'flow-content--email-only':
+                steps === 1 && !smsEnabled && !showSubStep,
+            }"
           >
             <LoginStep1Panel
               v-if="steps === 1"
               v-model:email="email"
               v-model:phone="phone"
+              :sms-enabled="smsEnabled"
             />
 
             <LoginStep2Panel
@@ -137,6 +142,7 @@
 <script setup>
 import { computed, onMounted, ref } from "vue";
 import { createUserAndProvider, sendCode, validateCode } from "src/api/user";
+import { getServiceConfiguration } from "src/api/serviceConfiguration";
 import { useAuth } from "src/composables/useAuth";
 import { useGeocodingApi } from "src/composables/useGeocodingApi";
 import { useI18n } from "vue-i18n";
@@ -170,6 +176,7 @@ const loginForm = ref(null);
 const phone = ref("");
 const registrationCompleted = ref(false);
 const showSubStep = ref(false);
+const smsEnabled = ref(false);
 
 const isPendingProvider = computed(
   () =>
@@ -211,6 +218,7 @@ const createInitialStepThreeForm = () => ({
   document: "",
   district: "",
   email: "",
+  gender: "",
   instructions: "",
   latitude: null,
   longitude: null,
@@ -245,6 +253,8 @@ const showBackButton = computed(
   () => registrationCompleted.value || steps.value >= 4,
 );
 
+//
+
 const goBack = async () => {
   if (registrationCompleted.value) {
     await logout();
@@ -281,11 +291,13 @@ const resetToLoginStart = async () => {
   loginForm.value?.resetValidation?.();
 };
 
-const hasWorkingDaySelected = () => mapWorkingDays().length > 0;
+//
 
 const hasCoordinateValue = (value) =>
   value !== null && value !== undefined && String(value).trim() !== "";
 
+const hasWorkingDaySelected = () => mapWorkingDays().length > 0;
+
 const ensureStepThreeCoordinates = async () => {
   const hasLatitude = hasCoordinateValue(stepThreeForm.value.latitude);
   const hasLongitude = hasCoordinateValue(stepThreeForm.value.longitude);
@@ -309,6 +321,23 @@ const ensureStepThreeCoordinates = async () => {
   stepThreeForm.value.longitude = geo.lng;
 };
 
+//
+
+const loadServiceConfiguration = async () => {
+  try {
+    const configuration = await getServiceConfiguration();
+
+    smsEnabled.value = configuration?.sms_enabled === true;
+  } catch (error) {
+    smsEnabled.value = false;
+    console.error("Erro ao carregar configuração dos serviços:", error);
+  }
+
+  if (!smsEnabled.value) {
+    phone.value = "";
+  }
+};
+
 const mapWorkingDays = () => {
   const mapped = [];
 
@@ -440,6 +469,7 @@ const registerUserAndProvider = async () => {
   append("rg", stepThreeForm.value.rg);
   append("document", stepThreeForm.value.document);
   append("birth_date", toISODate(stepThreeForm.value.birth_date));
+  append("gender", stepThreeForm.value.gender);
   append("zip_code", stepThreeForm.value.zip_code);
   append("address", stepThreeForm.value.address);
   append("number", stepThreeForm.value.number);
@@ -596,6 +626,15 @@ const validateCurrentStep = async () => {
 
   if (!isValid) return false;
 
+  if (steps.value === 3 && !stepThreeForm.value.gender) {
+    $q.notify({
+      message: t("provider.login.steps.step_3.gender_required"),
+      type: "negative",
+    });
+
+    return false;
+  }
+
   if (steps.value === 4) {
     const hasDocumentBack = !!stepFourForm.value.document_back;
     const hasDocumentFront = !!stepFourForm.value.document_front;
@@ -624,6 +663,8 @@ const validateCurrentStep = async () => {
 };
 
 onMounted(() => {
+  loadServiceConfiguration();
+
   if (user.accessToken && isPendingProvider.value) {
     registrationCompleted.value = true;
     clicked.value = true;
@@ -736,6 +777,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 {

+ 168 - 61
src/pages/calendar/CalendarPage.vue

@@ -2,59 +2,102 @@
   <q-page class="bg-page q-pb-xl">
     <div class="calendar-header row items-center bg-white">
       <q-space />
-      <span class="font16 fontbold gradient-diarista">{{ $t('provider.dashboard.agenda.title') }}</span>
+
+      <span class="font16 fontbold gradient-diarista">
+        {{ $t('provider.dashboard.agenda.title') }}
+      </span>
+
       <q-space />
     </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 font16 fontbold gradient-diarista q-mb-sm">{{ $t('provider.dashboard.agenda.upcoming_title') }}</div>
+        <div class="section-title font16 fontbold gradient-diarista q-mb-sm">
+          {{ $t('provider.dashboard.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.customer_photo" :src="item.customer_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.client_name)?.slice(0, 2).toUpperCase() ?? '??' }}</span>
+                  <img
+                    v-if="item.customer_photo"
+                    :src="item.customer_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.client_name)?.slice(0, 2).toUpperCase() ?? '??' }}
+                  </span>
                 </q-avatar>
 
                 <div class="col column text-text">
-                  <span class="font12 fontmedium ellipsis">{{ getFirstName(item.client_name) }}</span>
+                  <span class="font12 fontmedium ellipsis">
+                    {{ getFirstName(item.client_name) }}
+                  </span>
+
                   <div class="row items-center no-wrap">
-                    <span class="font9 fontbold">{{ formatWeekday(item.date) }}</span>
-                    <span class="font9 fontmedium">{{ ', ' + formatDayMonth(item.date) }}</span>
+                    <span class="font9 fontbold">
+                      {{ formatWeekday(item.date) }}
+                    </span>
+
+                    <span class="font9 fontmedium">
+                      {{ ', ' + formatDayMonth(item.date) }}
+                    </span>
                   </div>
+
                   <span class="font9 fontmedium">
                     {{ $t('common.from') }}
-                    <span class="font9 fontbold">{{ item.start_time?.slice(0, 5) }}</span>
+
+                    <span class="font9 fontbold">
+                      {{ item.start_time?.slice(0, 5) }}
+                    </span>
+
                     {{ $t('common.to') }}
-                    <span class="font9 fontbold">{{ item.end_time?.slice(0, 5) }}</span>
+
+                    <span class="font9 fontbold">
+                      {{ item.end_time?.slice(0, 5) }}
+                    </span>
                   </span>
                 </div>
 
                 <div class="col-auto column items-end">
                   <q-chip
+                    class="status-chip font9"
                     dense
                     square
                     :color="statusBgColor(item.status)"
-                    :text-color="statusTextColor(item.status)"
                     :label="statusLabel(item.status)"
-                    class="status-chip font9"
+                    :text-color="statusTextColor(item.status)"
                   />
-                  <span class="text-price font10 fontbold">{{ formatCurrency(item.total_amount) }}</span>
-                  <span class="text-period font9 fontmedium">{{ periodLabel(item.period_type) }}</span>
+
+                  <span class="text-price font10 fontbold">
+                    {{ formatCurrency(item.total_amount) }}
+                  </span>
+
+                  <span class="text-period font9 fontmedium">
+                    {{ periodLabel(item.period_type) }}
+                  </span>
                 </div>
               </div>
 
@@ -62,13 +105,15 @@
                 <span class="type-label text-primary font10 fontmedium">
                   {{ item.schedule_type === 'custom' ? $t('provider.dashboard.agenda.type_custom') : $t('provider.dashboard.agenda.type_default') }}
                 </span>
+
                 <q-space />
+
                 <q-btn
-                  rounded
-                  unelevated
-                  padding="4px 12px"
                   color="primary"
+                  padding="4px 12px"
+                  rounded
                   size="xs"
+                  unelevated
                   :label="$t('provider.dashboard.agenda.btn_view_details')"
                   @click="openDetailsDialog(item)"
                 />
@@ -83,47 +128,83 @@
       </div>
 
       <div class="q-mt-lg q-mx-md">
-        <div class="section-title font16 fontbold gradient-diarista q-mb-sm">{{ $t('provider.dashboard.agenda.completed_title') }}</div>
+        <div class="section-title font16 fontbold gradient-diarista q-mb-sm">
+          {{ $t('provider.dashboard.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.customer_photo" :src="item.customer_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.client_name)?.slice(0, 2).toUpperCase() ?? '??' }}</span>
+                  <img
+                    v-if="item.customer_photo"
+                    :src="item.customer_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.client_name)?.slice(0, 2).toUpperCase() ?? '??' }}
+                  </span>
                 </q-avatar>
 
                 <div class="col column text-text">
-                  <span class="font12 fontmedium ellipsis"> {{ getFirstName(item.client_name) || '—' }}</span>
+                  <span class="font12 fontmedium ellipsis">
+                    {{ getFirstName(item.client_name) || '—' }}
+                  </span>
+
                   <div class="row items-center no-wrap">
-                    <span class="font9 fontbold">{{ formatWeekday(item.date) }}</span>
-                    <span class="font9 fontmedium">{{ ', ' + formatDayMonth(item.date) }}</span>
+                    <span class="font9 fontbold">
+                      {{ formatWeekday(item.date) }}
+                    </span>
+
+                    <span class="font9 fontmedium">
+                      {{ ', ' + formatDayMonth(item.date) }}
+                    </span>
                   </div>
+
                   <span class="font9 fontmedium">
                     {{ $t('common.from') }}
-                    <span class="font9 fontbold">{{ item.start_time?.slice(0, 5) }}</span>
+
+                    <span class="font9 fontbold">
+                      {{ item.start_time?.slice(0, 5) }}
+                    </span>
+
                     {{ $t('common.to') }}
-                    <span class="font9 fontbold">{{ item.end_time?.slice(0, 5) }}</span>
+
+                    <span class="font9 fontbold">
+                      {{ item.end_time?.slice(0, 5) }}
+                    </span>
                   </span>
                 </div>
 
                 <div class="col-auto column items-end">
                   <q-chip
+                    class="status-chip font9"
                     dense
                     square
                     :color="statusBgColor(item.status)"
-                    :text-color="statusTextColor(item.status)"
                     :label="statusLabel(item.status)"
-                    class="status-chip font9"
+                    :text-color="statusTextColor(item.status)"
                   />
-                  <span class="text-price font10 fontbold">{{ formatCurrency(item.total_amount) }}</span>
-                  <span class="text-period font9 fontmedium">{{ periodLabel(item.period_type) }}</span>
+
+                  <span class="text-price font10 fontbold">
+                    {{ formatCurrency(item.total_amount) }}
+                  </span>
+
+                  <span class="text-period font9 fontmedium">
+                    {{ periodLabel(item.period_type) }}
+                  </span>
                 </div>
               </div>
 
@@ -131,26 +212,29 @@
                 <span class="type-label text-primary font10 fontmedium">
                   {{ item.schedule_type === 'custom' ? $t('provider.dashboard.agenda.type_custom') : $t('provider.dashboard.agenda.type_default') }}
                 </span>
+
                 <q-space />
+
                 <q-rating
                   :model-value="item.provider_reviewed ? item.provider_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"
                 />
+
                 <q-btn
                   v-if="!item.provider_reviewed"
-                  unelevated
-                  rounded
-                  padding="4px 12px"
-                  no-caps
+                  class="btn-rate"
                   color="secondary"
+                  no-caps
+                  padding="4px 12px"
+                  rounded
                   size="xs"
-                  class="btn-rate"
+                  unelevated
                   :label="$t('provider.dashboard.agenda.btn_rate')"
                   @click="openRatingDialog(item)"
                 />
@@ -167,13 +251,15 @@
   </q-page>
 </template>
 
+
 <script setup>
-import { ref, onMounted } from 'vue';
-import { useQuasar } from 'quasar';
-import { useI18n } from 'vue-i18n';
-import { getProviderCalendar } from 'src/api/providerCalendar';
 import { formatCurrency, getFirstName } from 'src/helpers/utils';
-import { labelsPeriodTypes } from 'src/helpers/arraysOptions/labelsPeriodTypes.js';
+import { getProviderCalendar } from 'src/api/providerCalendar';
+import { labelsPeriodTypes } from 'src/helpers/labelsPeriodTypes.js';
+import { onMounted, ref } from 'vue';
+import { useI18n } from 'vue-i18n';
+import { useQuasar } from 'quasar';
+
 import NextSchedulesDetailsDialog from 'src/components/dashboard/NextSchedulesDetailsDialog.vue';
 import ScheduleRatingDialog from 'src/components/dashboard/ScheduleRatingDialog.vue';
 
@@ -186,50 +272,54 @@ const avatarColors = [
   { 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})/);
+
   if (iso) return new Date(+iso[1], +iso[2] - 1, +iso[3]);
+
   const dmy = s.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 formatDayMonth = (dateStr) => {
   const d = parseLocalDate(dateStr);
+
   if (!d) return '';
-  const w = d.toLocaleDateString('pt-BR', { weekday: 'long' });
-  return w.charAt(0).toUpperCase() + w.slice(1);
+
+  return d.toLocaleDateString('pt-BR', { day: '2-digit', month: '2-digit' });
 };
 
-const formatDayMonth = (dateStr) => {
+const formatWeekday = (dateStr) => {
   const d = parseLocalDate(dateStr);
+
   if (!d) return '';
-  return d.toLocaleDateString('pt-BR', { day: '2-digit', month: '2-digit' });
+
+  const w = d.toLocaleDateString('pt-BR', { weekday: 'long' });
+
+  return w.charAt(0).toUpperCase() + w.slice(1);
 };
 
+//
+
 const periodLabel = (periodType) => {
   const found = labelsPeriodTypes.find(l => l.value == periodType);
   return found ? t(found.label) : '';
 };
 
-const statusLabel = (status) => {
-  const map = {
-    pending: t('provider.dashboard.agenda.status_pending'),
-    accepted: t('provider.dashboard.agenda.status_accepted'),
-    paid: t('provider.dashboard.agenda.status_paid'),
-    started: t('provider.dashboard.agenda.status_started'),
-    finished: t('provider.dashboard.agenda.status_finished'),
-    cancelled: t('provider.dashboard.agenda.status_cancelled'),
-  };
-  return map[status] ?? status;
-};
-
 const statusBgColor = (status) => {
   const map = {
     pending: 'warning-bg',
@@ -239,9 +329,23 @@ const statusBgColor = (status) => {
     finished: 'neutral-bg',
     cancelled: 'secondary-bg',
   };
+
   return map[status] ?? 'neutral-bg';
 };
 
+const statusLabel = (status) => {
+  const map = {
+    pending: t('provider.dashboard.agenda.status_pending'),
+    accepted: t('provider.dashboard.agenda.status_accepted'),
+    paid: t('provider.dashboard.agenda.status_paid'),
+    started: t('provider.dashboard.agenda.status_started'),
+    finished: t('provider.dashboard.agenda.status_finished'),
+    cancelled: t('provider.dashboard.agenda.status_cancelled'),
+  };
+
+  return map[status] ?? status;
+};
+
 const statusTextColor = (status) => {
   const map = {
     pending: 'warning',
@@ -251,11 +355,15 @@ const statusTextColor = (status) => {
     finished: 'status-finished',
     cancelled: 'secondary',
   };
+
   return map[status] ?? 'text';
 };
 
+//
+
 const loadCalendar = async () => {
   const response = await getProviderCalendar();
+
   if (response) {
     upcomingSchedules.value = response.upcomingSchedules ?? [];
     completedSchedules.value = response.completedSchedules ?? [];
@@ -282,7 +390,6 @@ const openRatingDialog = (schedule) => {
   });
 };
 
-
 onMounted(async () => {
   await loadCalendar();
   loading.value = false;

+ 188 - 107
src/pages/opportunities/OpportunitiesPage.vue

@@ -1,50 +1,113 @@
 <template>
   <q-page class="opportunities-page">
     <div class="page-header">
-      <q-btn flat round dense icon="chevron_left" class="back-btn" @click="router.back()" />
+      <q-btn
+        class="back-btn"
+        dense
+        flat
+        icon="chevron_left"
+        round
+        @click="router.back()"
+      />
+
       <div class="gradient-diarista font16 fontbold">
-        {{ $t('provider.dashboard.opportunities.title') }}
+        {{ $t("provider.dashboard.opportunities.title") }}
       </div>
     </div>
 
-    <q-card flat class="info-banner">
-      <q-icon name="mdi-auto-fix" size="30px" />
+    <q-card
+      class="info-banner"
+      flat
+    >
+      <q-icon
+        name="mdi-auto-fix"
+        size="30px"
+      />
+
       <div class="banner-text font10 fontmedium">
-        {{ $t('provider.dashboard.opportunities.banner_text') }}
+        {{ $t("provider.dashboard.opportunities.banner_text") }}
       </div>
     </q-card>
 
-    <div v-if="loading" class="flex flex-center q-pa-lg">
-      <q-spinner-dots color="secondary" size="32px" />
+    <div
+      v-if="loading"
+      class="flex flex-center q-pa-lg"
+    >
+      <q-spinner-dots
+        color="secondary"
+        size="32px"
+      />
     </div>
 
-    <div v-else-if="!opportunities.length" class="text-center q-pa-md font14 medium text-text">
-      {{ $t('provider.dashboard.opportunities.empty') }}
+    <div
+      v-else-if="!opportunities.length"
+      class="text-center q-pa-md font14 medium text-text"
+    >
+      {{ $t("provider.dashboard.opportunities.empty") }}
     </div>
 
-    <div v-else class="opportunity-list">
-      <q-card v-for="item in opportunities" :key="item.id" :flat="false" class="opportunity-card shadow-card bg-surface">
+    <div
+      v-else
+      class="opportunity-list"
+    >
+      <q-card
+        v-for="item in opportunities"
+        :key="item.id"
+        :flat="false"
+        class="opportunity-card shadow-card bg-surface"
+      >
         <div class="row">
           <div>
-            <img v-if="item.customer_photo" :src="item.customer_photo" alt="Avatar" class="client-avatar" />
-            <div v-else class="client-avatar client-avatar--initials font14 fontbold" :style="avatarColors[item.id % avatarColors.length]">{{ getFirstName(item.client_name)?.slice(0, 2).toUpperCase() ?? '??' }}</div>
+            <img
+              v-if="item.customer_photo"
+              alt="Avatar"
+              class="client-avatar"
+              :src="item.customer_photo"
+            />
+
+            <div
+              v-else
+              class="client-avatar client-avatar--initials font14 fontbold"
+              :style="avatarColors[item.id % avatarColors.length]"
+            >
+              {{ getFirstName(item.client_name)?.slice(0, 2).toUpperCase() ?? "??" }}
+            </div>
           </div>
+
           <div class="center-content q-pl-md">
             <div class="client-name-row">
-              <span class="text-text font12 fontmedium">{{ getFirstName(item.client_name) || '—' }}</span>
+              <span class="text-text font12 fontmedium">
+                {{ getFirstName(item.client_name) || "—" }}
+              </span>
 
               <span class="rating font9 fontregular">
-                <q-icon name="star" size="11px" />
+                <q-icon
+                  name="star"
+                  size="11px"
+                />
+
                 {{ item.client?.average_rating }}
               </span>
             </div>
 
             <div class="service-date text-text font9">
-              <span class="fontbold">{{ formatWeekday(item.date) }}</span><span>{{ ', ' + formatDayMonth(item.date) }}</span>
+              <span class="fontbold">
+                {{ formatWeekday(item.date) }}
+              </span>
+
+              <span>
+                {{ ", " + formatDayMonth(item.date) }}
+              </span>
             </div>
 
             <div class="service-hour text-text font9">
-              {{ $t('common.from') }} <span class="fontbold">{{ formatHour(item.start_time) }} {{ $t('common.to') }} {{ formatHour(item.end_time) }}</span>
+              {{ $t("common.from") }}
+
+              <span class="fontbold">
+                {{ formatHour(item.start_time) }}
+                {{ $t("common.to") }}
+                {{ formatHour(item.end_time) }}
+              </span>
             </div>
           </div>
 
@@ -53,138 +116,170 @@
               {{ filterCurrency(chooseprice(item.period_type, user.user.provider.daily_price_8h)) }}
             </div>
 
-            <div class="font9 fontmedium text-text">{{ t(labelsPeriodTypes.find(l => l.value == item.period_type)?.label) }}</div>
+            <div class="font9 fontmedium text-text">
+              {{ t(labelsPeriodTypes.find((label) => label.value == item.period_type)?.label) }}
+            </div>
 
             <div class="service-address font10 fontbold">
               {{ item.address?.district }}
             </div>
 
             <div class="distance font9">
-              {{ $t('provider.dashboard.opportunities.distance_km', { distance: item.distance_km ?? 0 }) }}
+              {{ $t("provider.dashboard.opportunities.distance_km", { distance: item.distance_km ?? 0 }) }}
             </div>
-
           </div>
-
-
         </div>
+
         <div class="row">
           <div class="q-my-auto text-primary font9 fontbold">
             {{ item.custom_schedule?.service_type.description }}
           </div>
-          <q-space/>
-          <q-btn unelevated rounded size="xs" padding="4px 12px" no-caps color="secondary" :label="$t('provider.dashboard.opportunities.details')" class="font11 fontsemibold" @click="goToOpportunityDetails(item)" />      
+
+          <q-space />
+
+          <q-btn
+            class="font11 fontsemibold"
+            color="secondary"
+            no-caps
+            padding="4px 12px"
+            rounded
+            size="xs"
+            unelevated
+            :label="$t('provider.dashboard.opportunities.details')"
+            @click="goToOpportunityDetails(item)"
+          />
         </div>
       </q-card>
     </div>
   </q-page>
 </template>
+
 <script setup>
-import { ref, onMounted } from 'vue'
-import { useQuasar } from 'quasar'
-import { useI18n } from 'vue-i18n'
-import { chooseprice, filterCurrency, getFirstName } from 'src/helpers/utils'
-import { getProviderOpportunities } from 'src/api/opportunities'
-import { userStore } from 'src/stores/user'
-import { labelsPeriodTypes } from 'src/helpers/arraysOptions/labelsPeriodTypes.js'
-import { useRouter } from 'vue-router'
-import OpportunityDetailsDialog from './components/OpportunityDetailsDialog.vue'
+import {
+  chooseprice,
+  filterCurrency,
+  getFirstName,
+} from "src/helpers/utils";
+
+import { getProviderOpportunities } from "src/api/opportunities";
+import { labelsPeriodTypes } from "src/helpers/labelsPeriodTypes.js";
+import { onMounted, ref } from "vue";
+import { useI18n } from "vue-i18n";
+import { useQuasar } from "quasar";
+import { useRouter } from "vue-router";
+import { userStore } from "src/stores/user";
+
+import OpportunityDetailsDialog from "./components/OpportunityDetailsDialog.vue";
 
 const $q = useQuasar();
 const user = userStore();
 const router = useRouter();
+
 const { t } = useI18n();
 
+const opportunities = ref([]);
+const loading = ref(false);
+
 const avatarColors = [
-  { background: '#ffd5df', color: '#932e57' },
-  { background: '#d7e8ff', color: '#2158a8' },
-  { background: '#dfd',    color: '#2a7a3b' },
-  { background: '#ffe5cc', color: '#8a4500' },
+  { background: "#ffd5df", color: "#932e57" },
+  { background: "#d7e8ff", color: "#2158a8" },
+  { background: "#dfd",    color: "#2a7a3b" },
+  { background: "#ffe5cc", color: "#8a4500" },
 ];
 
-const opportunities = ref([])
-const loading = ref(false)
-
 const goToOpportunityDetails = (item) => {
-  const id = item.custom_schedule?.id || item.id
+  const id = item.custom_schedule?.id || item.id;
 
   $q.dialog({
     component: OpportunityDetailsDialog,
     componentProps: {
       opportunityId: id,
       distanceKm: item.distance_km ?? null,
-    }
-  })
-}
+    },
+  });
+};
 
-const parseLocalDate = (iso) => {
-  if (!iso) return null;
-  const m = String(iso).match(/^(\d{4})-(\d{2})-(\d{2})/);
-  return m ? new Date(+m[1], +m[2] - 1, +m[3]) : null;
+const loadOpportunities = async () => {
+  loading.value = true;
+
+  try {
+    const response = await getProviderOpportunities(user.user.provider.id);
+
+    opportunities.value = response || [];
+  } catch (error) {
+    console.error("Erro ao buscar oportunidades:", error);
+
+    opportunities.value = [];
+  } finally {
+    loading.value = false;
+  }
 };
 
-const formatWeekday = (iso) => {
-  const d = parseLocalDate(iso);
-  if (!d) return '';
-  const w = d.toLocaleDateString('pt-BR', { weekday: 'long' });
-  return w.charAt(0).toUpperCase() + w.slice(1);
+//
+
+const parseLocalDate = (dateString) => {
+  if (!dateString) return null;
+
+  const date = String(dateString);
+  const isoDate = date.match(/^(\d{4})-(\d{2})-(\d{2})/);
+
+  if (!isoDate) return null;
+
+  return new Date(+isoDate[1], +isoDate[2] - 1, +isoDate[3]);
 };
 
-const formatDayMonth = (iso) => {
-  const d = parseLocalDate(iso);
-  if (!d) return '';
-  return d.toLocaleDateString('pt-BR', { day: '2-digit', month: '2-digit' });
+const formatDayMonth = (dateString) => {
+  const date = parseLocalDate(dateString);
+
+  if (!date) return "";
+
+  return date.toLocaleDateString("pt-BR", {
+    day: "2-digit",
+    month: "2-digit",
+  });
 };
 
 const formatHour = (time) => {
-  if (!time) return ''
-  return time.slice(0, 5)
-}
+  if (!time) return "";
 
+  return time.slice(0, 5);
+};
 
-const loadOpportunities = async () => {
-  loading.value = true
+const formatWeekday = (dateString) => {
+  const date = parseLocalDate(dateString);
 
-  try {
-    const response = await getProviderOpportunities(
-      user.user.provider.id
-    )
+  if (!date) return "";
 
-    opportunities.value = (response || [])
+  const weekday = date.toLocaleDateString("pt-BR", {
+    weekday: "long",
+  });
 
-  } catch (error) {
-    console.error('Erro ao buscar oportunidades:', error)
+  return weekday.charAt(0).toUpperCase() + weekday.slice(1);
+};
 
-    opportunities.value = []
+//
 
-  } finally {
-    loading.value = false
-  }
-}
-
-onMounted(loadOpportunities)
+onMounted(loadOpportunities);
 </script>
 
 <style scoped lang="scss">
 .opportunities-page {
+  min-height: 100vh;
   padding: 0;
   background: #f7f7fb;
-  min-height: 100vh;
 }
 
 .page-header {
+  position: relative;
+  z-index: 10;
   display: flex;
   align-items: center;
   justify-content: center;
-  position: relative;
-
   padding: 14px 16px;
+  margin-bottom: 16px;
   background: #fff;
-
-  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
   border-radius: 0 0 16px 16px;
-
-  margin-bottom: 16px;
-  z-index: 10;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
 }
 
 .back-btn {
@@ -197,10 +292,10 @@ onMounted(loadOpportunities)
   display: flex;
   gap: 12px;
   padding: 14px;
-  border-radius: 14px;
-  background: #a78bfa;
-  color: white;
   margin: 16px;
+  color: white;
+  background: #a78bfa;
+  border-radius: 14px;
 }
 
 .banner-text {
@@ -220,20 +315,13 @@ onMounted(loadOpportunities)
   gap: 14px;
   padding: 14px;
   border-radius: 18px;
-  // align-items: flex-start;
-}
-
-.avatar-column {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
 }
 
 .client-avatar {
   width: 54px;
   height: 54px;
-  border-radius: 50%;
   object-fit: cover;
+  border-radius: 50%;
 }
 
 .client-avatar--initials {
@@ -242,28 +330,22 @@ onMounted(loadOpportunities)
   justify-content: center;
 }
 
-
 .center-content {
-  flex: 1;
   display: flex;
+  flex: 1;
   flex-direction: column;
 }
 
 .client-name-row {
   display: flex;
-  align-items: center;
   gap: 4px;
-}
-
-.district {
-  margin-top: 4px;
-  color: #666;
+  align-items: center;
 }
 
 .rating {
   display: flex;
-  align-items: center;
   gap: 2px;
+  align-items: center;
   color: #ffb800;
 }
 
@@ -283,16 +365,15 @@ onMounted(loadOpportunities)
 }
 
 .service-address {
+  max-width: 120px;
   margin-top: 6px;
+  line-height: 1.3;
   color: #666;
   text-align: right;
-  line-height: 1.3;
-  max-width: 120px;
 }
 
 .distance {
   margin-top: 4px;
   color: #999;
 }
-
 </style>

+ 27 - 43
src/pages/opportunities/components/OpportunityDetailsDialog.vue

@@ -60,22 +60,11 @@
         </div>
 
         <div class="client-price fontbold q-pt-md text-primary">
-          {{
-            $t('provider.dashboard.opportunities.currency', {
-              value: choosePrice(details.schedule?.period_type)
-            })
-          }}
+          {{ $t('provider.dashboard.opportunities.currency', {value: choosePrice(details.schedule?.period_type)}) }}
         </div>
 
         <div class="font10 fontmedium text-grey-6">
-          {{
-            t(
-              labelsPeriodTypes.find(
-                (periodType) =>
-                  periodType.value === details.schedule?.period_type
-              )?.label
-            )
-          }}
+          {{ t(labelsPeriodTypes.find((periodType) => periodType.value === details.schedule?.period_type)?.label) }}
         </div>
 
         <div class="date font11">
@@ -111,11 +100,7 @@
       </div>
 
       <div class="distance font12 fontbold">
-        {{
-          $t('provider.dashboard.opportunity_details.distance_text', {
-            distance: distanceKm ?? 0
-          })
-        }}
+        {{ $t('provider.dashboard.opportunity_details.distance_text', { distance: distanceKm ?? 0 }) }}
       </div>
 
       <!-- TAGS -->
@@ -138,11 +123,7 @@
         {{ $t('provider.dashboard.opportunity_details.sob_medida') }}
 
         <span>
-          {{
-            $t(
-              'provider.dashboard.opportunity_details.sob_medida_highlight'
-            )
-          }}
+          {{ $t('provider.dashboard.opportunity_details.sob_medida_highlight') }}
         </span>
 
         <br />
@@ -250,16 +231,16 @@
 
 <script setup>
 import { Dialog, useDialogPluginComponent } from 'quasar'
-import { onMounted, ref } from 'vue'
-import { useI18n } from 'vue-i18n'
-import { useRouter } from 'vue-router'
 
 import {
   getOpportunityById,
   proposalOpportunity
 } from 'src/api/opportunities'
 
-import { labelsPeriodTypes } from 'src/helpers/arraysOptions/labelsPeriodTypes.js'
+import { labelsPeriodTypes } from 'src/helpers/labelsPeriodTypes.js'
+import { onMounted, ref } from 'vue'
+import { useI18n } from 'vue-i18n'
+import { useRouter } from 'vue-router'
 import { userStore } from 'src/stores/user'
 
 import RefuseOpportunityDialog from './RefuseOpportunityDialog.vue'
@@ -269,7 +250,6 @@ const props = defineProps({
     type: Number,
     default: null
   },
-
   opportunityId: {
     type: Number,
     required: true
@@ -296,7 +276,7 @@ const loading = ref(true)
 const avatarColors = [
   { background: '#ffd5df', color: '#932e57' },
   { background: '#d7e8ff', color: '#2158a8' },
-  { background: '#dfd', color: '#2a7a3b' },
+  { background: '#dfd',    color: '#2a7a3b' },
   { background: '#ffe5cc', color: '#8a4500' }
 ]
 
@@ -319,6 +299,8 @@ const choosePrice = (periodType) => {
   }
 }
 
+//
+
 // A data vem do backend no formato DD/MM/YYYY.
 const parseLocalDate = (date) => {
   if (!date) {
@@ -340,20 +322,6 @@ const parseLocalDate = (date) => {
   )
 }
 
-const formatWeekday = (date) => {
-  const parsedDate = parseLocalDate(date)
-
-  if (!parsedDate) {
-    return ''
-  }
-
-  const weekday = parsedDate.toLocaleDateString('pt-BR', {
-    weekday: 'long'
-  })
-
-  return weekday.charAt(0).toUpperCase() + weekday.slice(1)
-}
-
 const formatDayMonth = (date) => {
   const parsedDate = parseLocalDate(date)
 
@@ -375,6 +343,22 @@ const formatHour = (time) => {
   return time.slice(0, 5)
 }
 
+const formatWeekday = (date) => {
+  const parsedDate = parseLocalDate(date)
+
+  if (!parsedDate) {
+    return ''
+  }
+
+  const weekday = parsedDate.toLocaleDateString('pt-BR', {
+    weekday: 'long'
+  })
+
+  return weekday.charAt(0).toUpperCase() + weekday.slice(1)
+}
+
+//
+
 const addressIcon = (type) => {
   if (type === 'home') {
     return 'mdi-home-outline'

+ 463 - 222
src/pages/payments/PaymentsPage.vue

@@ -2,54 +2,96 @@
   <q-page class="bg-page q-pb-xl">
     <div class="payments-header row items-center bg-white">
       <q-space />
-      <span class="gradient-diarista font16 fontbold">{{ $t('provider.payments.title') }}</span>
+
+      <span class="gradient-diarista font16 fontbold">
+        {{ $t("provider.payments.title") }}
+      </span>
+
       <q-space />
     </div>
 
     <div class="q-mx-md q-mt-md">
-      <q-card class="earnings-card bg-primary" :flat="false" style="cursor: pointer;" @click="earningsExpanded = !earningsExpanded">
+      <q-card
+        class="earnings-card bg-primary"
+        style="cursor: pointer"
+        :flat="false"
+        @click="earningsExpanded = !earningsExpanded"
+      >
         <q-card-section class="q-pa-md">
           <div class="row items-center no-wrap">
-            <q-icon name="mdi-currency-usd" size="22px" color="white" class="q-mr-sm" />
-            <span class="text-white font16 fontbold">{{ $t('provider.payments.total_earnings_title') }}</span>
-            <q-space />
             <q-icon
-              :name="earningsExpanded ? 'mdi-chevron-up' : 'mdi-chevron-down'"
+              class="q-mr-sm"
+              color="white"
+              name="mdi-currency-usd"
               size="22px"
+            />
+
+            <span class="text-white font16 fontbold">
+              {{ $t("provider.payments.total_earnings_title") }}
+            </span>
+
+            <q-space />
+
+            <q-icon
               color="white"
+              size="22px"
+              :name="
+                earningsExpanded
+                  ? 'mdi-chevron-up'
+                  : 'mdi-chevron-down'
+              "
             />
           </div>
         </q-card-section>
 
         <q-slide-transition>
-          <div v-show="earningsExpanded" @click.stop>
-            <q-separator color="white" style="opacity: 0.2;" />
+          <div
+            v-show="earningsExpanded"
+            @click.stop
+          >
+            <q-separator
+              color="white"
+              style="opacity: 0.2"
+            />
+
             <q-card-section class="q-pa-md">
               <div class="earnings-value font28 fontbold text-white">
                 {{ formatCurrency(earningsByPeriod[selectedPeriod].value) }}
               </div>
+
               <div class="earnings-count font12 text-white q-mb-md">
-                {{ $t('provider.payments.total_earnings_count', { count: earningsByPeriod[selectedPeriod].count }) }}
+                {{ $t("provider.payments.total_earnings_count", { count: earningsByPeriod[selectedPeriod].count }) }}
               </div>
 
               <div class="row q-gutter-x-xs q-mb-md">
                 <q-btn
                   v-for="period in periods"
                   :key="period.key"
-                  dense
-                  no-caps
-                  :unelevated="selectedPeriod === period.key"
+                  :color="
+                    selectedPeriod === period.key
+                      ? 'white'
+                      : undefined
+                  "
                   :flat="selectedPeriod !== period.key"
-                  :rounded="true"
-                  :color="selectedPeriod === period.key ? 'white' : undefined"
-                  :text-color="selectedPeriod === period.key ? 'primary' : 'white'"
                   :label="$t(period.labelKey)"
+                  :rounded="true"
+                  :text-color="
+                    selectedPeriod === period.key
+                      ? 'primary'
+                      : 'white'
+                  "
+                  :unelevated="selectedPeriod === period.key"
+                  dense
+                  no-caps
                   @click.stop="selectedPeriod = period.key"
                 />
               </div>
 
-              <div class="text-white font12" style="opacity: 0.8;">
-                {{ $t('provider.payments.total_earnings_days', { days: periodDays[selectedPeriod] }) }}
+              <div
+                class="text-white font12"
+                style="opacity: 0.8"
+              >
+                {{ $t("provider.payments.total_earnings_days", { days: periodDays[selectedPeriod] }) }}
               </div>
             </q-card-section>
           </div>
@@ -58,44 +100,75 @@
     </div>
 
     <div class="q-mx-md q-mt-sm">
-      <q-card class="balance-card bg-surface shadow-card" :flat="false">
+      <q-card
+        class="balance-card bg-surface shadow-card"
+        :flat="false"
+      >
         <q-card-section class="q-pa-md">
           <div class="row items-center no-wrap q-mb-xs">
             <div class="col">
-              <div class="text-grey-6 font12 fontbold">{{ $t('provider.payments.available_balance_label') }}</div>
-              <div class="balance-value fontbold text-secondary">{{ formatCurrency(saldoDisponivel) }}</div>
+              <div class="text-grey-6 font12 fontbold">
+                {{ $t("provider.payments.available_balance_label") }}
+              </div>
+
+              <div class="balance-value fontbold text-secondary">
+                {{ formatCurrency(saldoDisponivel) }}
+              </div>
             </div>
+
             <q-btn
-              unelevated
-              no-caps
-              color="secondary"
               class="btn-withdraw font13 fontbold"
+              color="secondary"
+              no-caps
+              unelevated
+              :disable="!canWithdraw"
               :label="$t('provider.payments.btn_withdraw')"
               :loading="withdrawLoading"
-              :disable="!canWithdraw"
               @click="onSacar"
             />
           </div>
+
           <div class="text-grey-6 font12">
-            {{ $t('provider.payments.available_balance_prefix') }}
-            <strong>{{ $t('provider.payments.available_balance_highlight') }}</strong>
+            {{ $t("provider.payments.available_balance_prefix") }}
+
+            <strong>
+              {{ $t("provider.payments.available_balance_highlight") }}
+            </strong>
           </div>
         </q-card-section>
       </q-card>
     </div>
 
     <div class="q-mx-md q-mt-sm">
-      <q-card class="balance-card bg-secondary shadow-card" :flat="false">
+      <q-card
+        class="balance-card bg-secondary shadow-card"
+        :flat="false"
+      >
         <q-card-section class="q-pa-md">
           <div class="row items-center no-wrap">
             <div class="col">
-              <div class="text-white font12">{{ $t('provider.payments.pending_balance_label') }}</div>
-              <div class="balance-value fontbold text-white">{{ formatCurrency(saldoALiberar) }}</div>
-              <div class="text-white font12 q-mt-xs" style="opacity: 0.85;">
-                {{ $t('provider.payments.pending_balance_desc') }}
+              <div class="text-white font12">
+                {{ $t("provider.payments.pending_balance_label") }}
+              </div>
+
+              <div class="balance-value fontbold text-white">
+                {{ formatCurrency(saldoALiberar) }}
+              </div>
+
+              <div
+                class="text-white font12 q-mt-xs"
+                style="opacity: 0.85"
+              >
+                {{ $t("provider.payments.pending_balance_desc") }}
               </div>
             </div>
-            <q-icon name="mdi-clock-outline" color="white" size="32px" style="opacity: 0.7;" />
+
+            <q-icon
+              color="white"
+              name="mdi-clock-outline"
+              size="32px"
+              style="opacity: 0.7"
+            />
           </div>
         </q-card-section>
       </q-card>
@@ -103,24 +176,37 @@
 
     <div class="q-mx-md q-mt-md">
       <div class="row items-center no-wrap q-mb-sm">
-        <span class="section-title font16 fontbold gradient-diarista">{{ $t('provider.payments.services_title') }}</span>
+        <span class="section-title font16 fontbold gradient-diarista">
+          {{ $t("provider.payments.services_title") }}
+        </span>
+
         <q-space />
+
         <q-btn
+          color="primary"
           flat
+          icon-right="mdi-chevron-right"
           no-caps
-          color="primary"
           size="sm"
           :label="$t('provider.payments.btn_view_movements')"
-          icon-right="mdi-chevron-right"
           @click="openMovimentacoes"
         />
       </div>
 
-      <div v-if="servicesLoading" class="flex flex-center q-py-xl">
-        <q-spinner color="primary" size="40px" />
+      <div
+        v-if="servicesLoading"
+        class="flex flex-center q-py-xl"
+      >
+        <q-spinner
+          color="primary"
+          size="40px"
+        />
       </div>
 
-      <template v-for="item in services" :key="item.id">
+      <template
+        v-for="item in services"
+        :key="item.id"
+      >
         <q-card
           class="service-card bg-surface shadow-card q-mb-sm"
           :flat="false"
@@ -128,33 +214,63 @@
           <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.client_photo" :src="item.client_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%">{{ item.client_name?.slice(0,2).toUpperCase() ?? '??' }}</span>
+                <img
+                  v-if="item.client_photo"
+                  :src="item.client_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]"
+                >
+                  {{ item.client_name?.slice(0, 2).toUpperCase() ?? "??" }}
+                </span>
               </q-avatar>
 
               <div class="col column">
-                <span class="text-name font13 fontbold ellipsis">{{ item.client_name }}</span>
-                <div class="">
-                  <span class="">{{ $t('provider.payments.services_date_service') }}</span>
-                  {{ ' ' + item.date }}
+                <span class="text-name font13 fontbold ellipsis">
+                  {{ item.client_name }}
+                </span>
+
+                <div>
+                  <span>
+                    {{ $t("provider.payments.services_date_service") }}
+                  </span>
+
+                  {{ " " + item.date }}
                 </div>
-                <div class="">
-                  <span class="">{{ $t('provider.payments.services_date_payment') }}</span>
-                  {{ ' ' + item.payment_date }}
+
+                <div>
+                  <span>
+                    {{ $t("provider.payments.services_date_payment") }}
+                  </span>
+
+                  {{ " " + item.payment_date }}
                 </div>
               </div>
 
               <div class="col-auto column items-end">
                 <q-chip
+                  class="status-chip font11 fontbold"
                   dense
                   square
                   :color="payStatusBgColor(item.payment_status)"
-                  :text-color="payStatusTextColor(item.payment_status)"
                   :label="payStatusLabel(item.payment_status)"
-                  class="status-chip font11 fontbold"
+                  :text-color="
+                    payStatusTextColor(item.payment_status)
+                  "
                 />
-                <span class="text-price font13 fontbold">{{ formatCurrency(item.total_amount) }}</span>
-                <span class="text-period font10">{{ item.period_label }}</span>
+
+                <span class="text-price font13 fontbold">
+                  {{ formatCurrency(item.total_amount) }}
+                </span>
+
+                <span class="text-period font10">
+                  {{ item.period_label }}
+                </span>
               </div>
             </div>
           </q-card-section>
@@ -165,153 +281,162 @@
 </template>
 
 <script setup>
-import { computed, ref, onMounted } from 'vue';
-import { useQuasar } from 'quasar';
-import { useI18n } from 'vue-i18n';
-import { formatCurrency } from 'src/helpers/utils';
-import { labelsPeriodTypes } from 'src/helpers/arraysOptions/labelsPeriodTypes.js';
-import { getBalance, getWithdrawalFees, requestWithdrawal } from 'src/api/providerWithdrawal';
-import { getPaymentSplits } from 'src/api/paymentSplit';
-import MovimentacoesDialog from 'src/components/payments/MovimentacoesDialog.vue';
-import WithdrawConfirmDialog from 'src/components/payments/WithdrawConfirmDialog.vue';
-
-const $q = useQuasar();
-const { t } = useI18n();
-
-const avatarColors = [
-  { background: '#ffd5df', color: '#932e57' },
-  { background: '#d7e8ff', color: '#2158a8' },
-  { background: '#dfd',    color: '#2a7a3b' },
-  { background: '#ffe5cc', color: '#8a4500' },
-];
-const earningsExpanded = ref(false);
-const selectedPeriod = ref('week');
-const servicesLoading = ref(true);
-const withdrawLoading = ref(false);
-
-const periods = [
-  { key: 'week',  labelKey: 'provider.payments.period_week'  },
-  { key: 'month', labelKey: 'provider.payments.period_month' },
-  { key: 'year',  labelKey: 'provider.payments.period_year'  },
-];
+import { computed, onMounted, ref } from "vue";
+import { formatCurrency } from "src/helpers/utils";
 
-const periodDays = { week: 7, month: 30, year: 365 };
+import {
+  getBalance,
+  getWithdrawalFees,
+  requestWithdrawal,
+} from "src/api/providerWithdrawal";
 
-const earningsByPeriod = ref({
-  week:  { value: 0, count: 0 },
-  month: { value: 0, count: 0 },
-  year:  { value: 0, count: 0 },
-});
+import { getPaymentSplits } from "src/api/paymentSplit";
+import { labelsPeriodTypes } from "src/helpers/labelsPeriodTypes.js";
+import { useI18n } from "vue-i18n";
+import { useQuasar } from "quasar";
 
-const saldoDisponivel = ref(0);
-const saldoALiberar = ref(0);
-const paymentTransferFeeAmount = ref(3.67);
-const services = ref([]);
+import MovimentacoesDialog from "src/components/payments/MovimentacoesDialog.vue";
+import WithdrawConfirmDialog from "src/components/payments/WithdrawConfirmDialog.vue";
 
-const parseAmount = (value) => {
-  const amount = Number.parseFloat(value);
-  return Number.isFinite(amount) ? amount : 0;
-};
-
-const roundMoney = (value) => Math.round((parseAmount(value) + Number.EPSILON) * 100) / 100;
-
-const withdrawalNetAmount = computed(() => (
-  Math.max(0, roundMoney(saldoDisponivel.value - paymentTransferFeeAmount.value))
-));
+const $q = useQuasar();
 
-const canWithdraw = computed(() => (
-  saldoDisponivel.value > 0
-  && withdrawalNetAmount.value > 0
-  && !withdrawLoading.value
-));
+const { t } = useI18n();
 
-const splitAmount = (split) => (
-  parseAmount(split.provider_amount ?? split.net_amount ?? split.gross_amount)
-);
+const earningsExpanded = ref(false);
+const selectedPeriod   = ref("week");
+const servicesLoading  = ref(true);
+const withdrawLoading  = ref(false);
 
-const splitPaymentStatus = (split) => (
-  split.payment_status ?? split.payment?.status ?? 'pending'
-);
+const saldoDisponivel = ref(0);
+const saldoALiberar   = ref(0);
 
-const splitScheduleStatus = (split) => (
-  split.schedule_status ?? split.schedule?.status ?? split.scheduleStatus ?? ''
-);
+const paymentTransferFeeAmount = ref(3.67);
 
-const splitCodeVerified = (split) => (
-  split.code_verified ?? split.schedule?.code_verified ?? false
-);
+const services = ref([]);
 
-const isTruthyFlag = (value) => (
-  value === true || value === 1 || value === '1' || value === 'true'
-);
+const earningsByPeriod = ref({
+  week: {
+    value: 0,
+    count: 0,
+  },
+  month: {
+    value: 0,
+    count: 0,
+  },
+  year: {
+    value: 0,
+    count: 0,
+  },
+});
 
-const isSplitPaidAndFinished = (split) => (
-  splitPaymentStatus(split) === 'paid'
-  && (!splitScheduleStatus(split) || splitScheduleStatus(split) === 'finished')
-  && isTruthyFlag(splitCodeVerified(split))
+const withdrawalNetAmount = computed(() =>
+  Math.max(
+    0,
+    roundMoney(
+      saldoDisponivel.value - paymentTransferFeeAmount.value,
+    ),
+  ),
 );
 
-const splitReleaseDate = (split) => (
-  split.available_at
-  ?? split.release_at
-  ?? split.released_at
-  ?? split.withdraw_available_at
-  ?? null
+const canWithdraw = computed(
+  () =>
+    saldoDisponivel.value > 0 &&
+    withdrawalNetAmount.value > 0 &&
+    !withdrawLoading.value,
 );
 
-const isSplitAvailable = (split) => {
-  if (!isSplitPaidAndFinished(split)) return false;
+const avatarColors = [
+  { background: "#ffd5df", color: "#932e57" },
+  { background: "#d7e8ff", color: "#2158a8" },
+  { background: "#dfd",    color: "#2a7a3b" },
+  { background: "#ffe5cc", color: "#8a4500" },
+];
 
-  const releaseAt = splitReleaseDate(split);
-  if (!releaseAt) return true;
+const periods = [
+  {
+    key: "week",
+    labelKey: "provider.payments.period_week",
+  },
+  {
+    key: "month",
+    labelKey: "provider.payments.period_month",
+  },
+  {
+    key: "year",
+    labelKey: "provider.payments.period_year",
+  },
+];
 
-  const releaseDate = parseDateSafe(releaseAt);
-  return !releaseDate || releaseDate <= new Date();
+const periodDays = {
+  week: 7,
+  month: 30,
+  year: 365,
 };
 
-const sumSplits = (splits, predicate) => (
-  splits.reduce((total, split) => total + (predicate(split) ? splitAmount(split) : 0), 0)
-);
-
 const loadData = async () => {
   servicesLoading.value = true;
+
   try {
     const [balance, splits, fees] = await Promise.all([
       getBalance(),
       getPaymentSplits(),
       loadWithdrawalFees(),
     ]);
+
     const paymentSplits = splits || [];
-    const availableFromSplits = sumSplits(paymentSplits, isSplitAvailable);
+
+    const availableFromSplits = sumSplits(
+      paymentSplits,
+      isSplitAvailable,
+    );
+
     const pendingFromSplits = sumSplits(
       paymentSplits,
-      (split) => isSplitPaidAndFinished(split) && !isSplitAvailable(split)
+      (split) =>
+        isSplitPaidAndFinished(split) &&
+        !isSplitAvailable(split),
     );
 
-    saldoDisponivel.value = parseAmount(balance?.available) || availableFromSplits;
-    saldoALiberar.value = parseAmount(balance?.pending) || pendingFromSplits;
-    paymentTransferFeeAmount.value = parseAmount(
-      fees?.payment_transfer_fee_amount
-      ?? fees?.gateway_fee_amount
-      ?? fees?.fee_amount
-      ?? fees?.amount
-    ) || paymentTransferFeeAmount.value;
-
-    services.value = paymentSplits.map((s) => ({
-      id: s.id,
-      client_name: s.client_name ?? t('provider.payments.default_client_name'),
+    saldoDisponivel.value =
+      parseAmount(balance?.available) || availableFromSplits;
+
+    saldoALiberar.value =
+      parseAmount(balance?.pending) || pendingFromSplits;
+
+    paymentTransferFeeAmount.value =
+      parseAmount(
+        fees?.payment_transfer_fee_amount ??
+          fees?.gateway_fee_amount ??
+          fees?.fee_amount ??
+          fees?.amount,
+      ) || paymentTransferFeeAmount.value;
+
+    services.value = paymentSplits.map((split) => ({
+      id: split.id,
+      client_name:
+        split.client_name ??
+        t("provider.payments.default_client_name"),
       client_photo: null,
-      date: formatServiceDate(s.schedule_date),
-      payment_date: formatIsoDate(s.payment_paid_at),
-      total_amount: splitAmount(s),
-      period_label: s.schedule_period_type
-        ? t(labelsPeriodTypes.find(l => l.value == s.schedule_period_type)?.label)
-        : '',
-      schedule_type: 'default',
-      payment_status: splitPaymentStatus(s),
+      date: formatServiceDate(split.schedule_date),
+      payment_date: formatIsoDate(split.payment_paid_at),
+      total_amount: splitAmount(split),
+      period_label: split.schedule_period_type
+        ? t(
+            labelsPeriodTypes.find(
+              (label) =>
+                label.value == split.schedule_period_type,
+            )?.label,
+          )
+        : "",
+      schedule_type: "default",
+      payment_status: splitPaymentStatus(split),
     }));
   } catch (error) {
-    $q.notify({ type: 'negative', message: error?.response?.data?.message || error.message });
+    $q.notify({
+      type: "negative",
+      message:
+        error?.response?.data?.message || error.message,
+    });
   } finally {
     servicesLoading.value = false;
   }
@@ -321,13 +446,20 @@ const loadWithdrawalFees = async () => {
   try {
     return await getWithdrawalFees();
   } catch {
-    return { payment_transfer_fee_amount: paymentTransferFeeAmount.value };
+    return {
+      payment_transfer_fee_amount:
+        paymentTransferFeeAmount.value,
+    };
   }
 };
 
 const onSacar = () => {
   const grossAmount = roundMoney(saldoDisponivel.value);
-  const gatewayFeeAmount = roundMoney(paymentTransferFeeAmount.value);
+
+  const gatewayFeeAmount = roundMoney(
+    paymentTransferFeeAmount.value,
+  );
+
   const netAmount = roundMoney(withdrawalNetAmount.value);
 
   $q.dialog({
@@ -339,113 +471,226 @@ const onSacar = () => {
     },
   }).onOk(async () => {
     withdrawLoading.value = true;
+
     try {
       await requestWithdrawal({
         gross_amount: grossAmount,
         gateway_fee_amount: gatewayFeeAmount,
         net_amount: netAmount,
       });
+
       saldoDisponivel.value = 0;
-      // notification handled by axios interceptor
     } catch (error) {
-      $q.notify({ type: 'negative', message: error?.response?.data?.message || error.message });
+      $q.notify({
+        type: "negative",
+        message:
+          error?.response?.data?.message || error.message,
+      });
     } finally {
       withdrawLoading.value = false;
     }
   });
 };
 
+const openMovimentacoes = () => {
+  $q.dialog({
+    component: MovimentacoesDialog,
+  });
+};
+
+const parseAmount = (value) => {
+  const amount = Number.parseFloat(value);
+
+  return Number.isFinite(amount) ? amount : 0;
+};
+
+const roundMoney = (value) =>
+  Math.round(
+    (parseAmount(value) + Number.EPSILON) * 100,
+  ) / 100;
+
+const splitAmount = (split) =>
+  parseAmount(
+    split.provider_amount ??
+      split.net_amount ??
+      split.gross_amount,
+  );
+
+const splitPaymentStatus = (split) =>
+  split.payment_status ??
+  split.payment?.status ??
+  "pending";
+
+const splitScheduleStatus = (split) =>
+  split.schedule_status ??
+  split.schedule?.status ??
+  split.scheduleStatus ??
+  "";
+
+const splitCodeVerified = (split) =>
+  split.code_verified ??
+  split.schedule?.code_verified ??
+  false;
+
+//
+
+const isSplitAvailable = (split) => {
+  if (!isSplitPaidAndFinished(split)) return false;
+
+  const releaseAt = splitReleaseDate(split);
+
+  if (!releaseAt) return true;
+
+  const releaseDate = parseDateSafe(releaseAt);
+
+  return !releaseDate || releaseDate <= new Date();
+};
+
+const isTruthyFlag = (value) =>
+  value === true ||
+  value === 1 ||
+  value === "1" ||
+  value === "true";
+
+const isSplitPaidAndFinished = (split) =>
+  splitPaymentStatus(split) === "paid" &&
+  (!splitScheduleStatus(split) ||
+    splitScheduleStatus(split) === "finished") &&
+  isTruthyFlag(splitCodeVerified(split));
+
+const splitReleaseDate = (split) =>
+  split.available_at ??
+  split.release_at ??
+  split.released_at ??
+  split.withdraw_available_at ??
+  null;
+
+const sumSplits = (splits, predicate) =>
+  splits.reduce(
+    (total, split) =>
+      total +
+      (predicate(split) ? splitAmount(split) : 0),
+    0,
+  );
+
+//
+
 const parseDateSafe = (value) => {
   if (!value) return null;
+
   const raw = String(value).trim();
 
-  // yyyy-mm-dd (from API date fields)
   if (/^\d{4}-\d{2}-\d{2}$/.test(raw)) {
     const date = new Date(`${raw}T12:00:00`);
+
     return Number.isNaN(date.getTime()) ? null : date;
   }
 
-  // dd/mm/yyyy
-  const brMatch = raw.match(/^(\d{2})\/(\d{2})\/(\d{4})$/);
-  if (brMatch) {
-    const [, day, month, year] = brMatch;
-    const date = new Date(`${year}-${month}-${day}T12:00:00`);
+  const brDate = raw.match(
+    /^(\d{2})\/(\d{2})\/(\d{4})$/,
+  );
+
+  if (brDate) {
+    const [, day, month, year] = brDate;
+    const date = new Date(
+      `${year}-${month}-${day}T12:00:00`,
+    );
+
     return Number.isNaN(date.getTime()) ? null : date;
   }
 
   const date = new Date(raw);
+
   return Number.isNaN(date.getTime()) ? null : date;
 };
 
-const formatShortDate = (dateStr) => {
-  const date = parseDateSafe(dateStr);
-  if (!date) return '';
-  return date.toLocaleDateString('pt-BR', { day: '2-digit', month: '2-digit' });
+
+const formatShortDate = (dateString) => {
+  const date = parseDateSafe(dateString);
+
+  if (!date) return "";
+
+  return date.toLocaleDateString("pt-BR", {
+    day: "2-digit",
+    month: "2-digit",
+  });
 };
 
-const formatServiceDate = (dateStr) => formatShortDate(dateStr);
-const formatIsoDate = (dateStr) => formatShortDate(dateStr);
+const formatIsoDate = (dateString) =>
+  formatShortDate(dateString);
 
-const payStatusLabel = (status) => {
-  const map = {
-    pending:    t('provider.payments.pay_status_pending'),
-    paid:       t('provider.payments.pay_status_paid'),
-    authorized: t('provider.payments.pay_status_authorized'),
-    processing: t('provider.payments.pay_status_processing'),
-    failed:     t('provider.payments.pay_status_failed'),
-    cancelled:  t('provider.payments.pay_status_cancelled'),
+const formatServiceDate = (dateString) =>
+  formatShortDate(dateString);
+
+//
+
+
+const payStatusBgColor = (status) => {
+  const colors = {
+    pending: "warning-bg",
+    paid: "success-bg",
+    authorized: "teal-bg",
+    processing: "info-bg",
+    failed: "error-bg",
+    cancelled: "neutral-bg",
   };
-  return map[status] ?? status;
+
+  return colors[status] ?? "neutral-bg";
 };
 
-const payStatusBgColor = (status) => {
-  const map = {
-    pending:    'warning-bg',
-    paid:       'success-bg',
-    authorized: 'teal-bg',
-    processing: 'info-bg',
-    failed:     'error-bg',
-    cancelled:  'neutral-bg',
+const payStatusLabel = (status) => {
+  const labels = {
+    pending: t("provider.payments.pay_status_pending"),
+    paid: t("provider.payments.pay_status_paid"),
+    authorized: t(
+      "provider.payments.pay_status_authorized",
+    ),
+    processing: t(
+      "provider.payments.pay_status_processing",
+    ),
+    failed: t("provider.payments.pay_status_failed"),
+    cancelled: t(
+      "provider.payments.pay_status_cancelled",
+    ),
   };
-  return map[status] ?? 'neutral-bg';
+
+  return labels[status] ?? status;
 };
 
 const payStatusTextColor = (status) => {
-  const map = {
-    pending:    'warning',
-    paid:       'success',
-    authorized: 'teal',
-    processing: 'info',
-    failed:     'error-dark',
-    cancelled:  'status-finished',
+  const colors = {
+    pending: "warning",
+    paid: "success",
+    authorized: "teal",
+    processing: "info",
+    failed: "error-dark",
+    cancelled: "status-finished",
   };
-  return map[status] ?? 'text';
-};
 
-const openMovimentacoes = () => {
-  $q.dialog({ component: MovimentacoesDialog });
+  return colors[status] ?? "text";
 };
 
-onMounted(() => {
-  loadData();
-});
+//
+
+onMounted(loadData);
 </script>
 
 <style scoped lang="scss">
 .payments-header {
   padding-top: 12px;
   padding-bottom: 12px;
-  box-shadow: 0px 1px 8px rgba(0, 0, 0, 0.08);
+  box-shadow: 0 1px 8px rgba(0, 0, 0, 0.08);
 }
 
 .earnings-card,
 .balance-card,
 .service-card {
-  border-radius: 12px;
   width: 100%;
+  border-radius: 12px;
 }
 
-.earnings-value {
+.earnings-value,
+.balance-value {
   line-height: 1.2;
 }
 
@@ -453,19 +698,15 @@ onMounted(() => {
   opacity: 0.9;
 }
 
-.balance-value {
-  line-height: 1.2;
-}
-
 .btn-withdraw {
-  border-radius: 8px !important;
   padding: 6px 18px;
+  border-radius: 8px !important;
 }
 
 .text-name {
-  color: #3a3a4a;
   max-width: 130px;
   overflow: hidden;
+  color: #3a3a4a;
   text-overflow: ellipsis;
   white-space: nowrap;
 }
@@ -483,6 +724,6 @@ onMounted(() => {
 
 .status-chip {
   height: auto;
-  padding: 2px 2px;
+  padding: 2px;
 }
-</style>
+</style>

+ 170 - 71
src/pages/profile/ProfileEditDialog.vue

@@ -1,77 +1,151 @@
 <template>
-  <q-dialog ref="dialogRef" persistent maximized transition-show="slide-left" transition-hide="slide-right">
-    <q-card class="bg-white full-width full-height">
-      <div class="row items-center q-px-md q-pb-sm bg-white shadow-profile dialog-safe-header">
-        <q-btn flat round dense icon="mdi-chevron-left" color="primary" @click="onDialogCancel" />
+  <q-dialog
+    ref="dialogRef"
+    maximized
+    persistent
+    transition-hide="slide-right"
+    transition-show="slide-left"
+  >
+    <q-card class="bg-white full-height full-width">
+      <div
+        class="bg-surface bg-white dialog-safe-header items-center q-pb-sm q-px-md row shadow-profile"
+      >
+        <q-btn
+          color="primary"
+          dense
+          flat
+          icon="mdi-chevron-left"
+          round
+          @click="onDialogCancel"
+        />
+
         <q-space />
-        <span class="font16 fontbold gradient-diarista">{{ $t('profile.edit_data') }}</span>
+
+        <span class="font16 fontbold gradient-diarista">
+          {{ $t("profile.edit_data") }}
+        </span>
+
         <q-space />
-        <div style="width: 32px"></div>
+
+        <div style="width: 32px" />
       </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)">
-          <div class="column items-center q-mt-xl q-mb-md">
-            <q-avatar size="140px" class="shadow-1" :style="!profilePhoto ? avatarStyle : undefined">
-              <img v-if="profilePhoto" :src="profilePhoto" style="object-fit:cover;width:100%;height:100%;border-radius:50%" />
-              <span v-else style="color: #7a154f">{{ form.name ? form.name.slice(0, 2).toUpperCase() : '' }}</span>
+        <q-scroll-area
+          class="col"
+          style="height: calc(100vh - 72px)"
+        >
+          <div class="column items-center q-mb-md q-mt-xl">
+            <q-avatar
+              class="shadow-1"
+              size="140px"
+              :style="!profilePhoto ? avatarStyle : undefined"
+            >
+              <img
+                v-if="profilePhoto"
+                :src="profilePhoto"
+                style="border-radius: 50%; height: 100%; object-fit: cover; width: 100%"
+              />
+
+              <span
+                v-else
+                style="color: #7a154f"
+              >
+                {{ form.name ? form.name.slice(0, 2).toUpperCase() : "" }}
+              </span>
             </q-avatar>
-            <q-btn flat no-caps color="grey-6" class="q-mt-sm font12 fontregular" :label="$t('profile.change_photo')" />
+
+            <q-btn
+              class="font12 fontregular q-mt-sm"
+              color="grey-6"
+              flat
+              no-caps
+              :label="$t('profile.change_photo')"
+            />
           </div>
 
-          <div class="q-px-xl q-gutter-y-lg">
+          <div class="q-gutter-y-lg q-px-xl">
             <div>
-              <div class="text-grey-8 q-mb-sm font12 fontbold">{{ $t('profile.full_name') }}</div>
+              <div class="font12 fontbold q-mb-sm text-grey-8">
+                {{ $t("profile.full_name") }}
+              </div>
+
               <q-input
                 v-model="form.name"
-                outlined
                 dense
                 input-class="text-text"
+                outlined
                 :placeholder="$t('profile.placeholder_name')"
               />
             </div>
 
             <div>
-              <div class="text-grey-8 q-mb-sm font12 fontbold">{{ $t('profile.email') }}</div>
+              <div class="font12 fontbold q-mb-sm text-grey-8">
+                {{ $t("profile.email") }}
+              </div>
+
               <q-input
                 v-model="form.email"
-                outlined
                 dense
                 input-class="text-text"
+                outlined
                 :placeholder="$t('profile.placeholder_email')"
               />
             </div>
 
             <div>
-              <div class="text-grey-8 q-mb-sm font12 fontbold">{{ $t('profile.phone') }}</div>
+              <div class="font12 fontbold q-mb-sm text-grey-8">
+                {{ $t("profile.phone") }}
+              </div>
+
               <q-input
                 v-model="form.phone"
-                outlined
                 dense
                 input-class="text-text"
                 mask="(##) #####-####"
+                outlined
                 unmasked-value
                 :placeholder="$t('profile.placeholder_phone')"
               />
             </div>
+
+            <div>
+              <div class="font12 fontbold q-mb-sm text-grey-8">
+                {{ $t("common.gender") }}
+              </div>
+
+              <q-option-group
+                v-model="form.gender"
+                class="gender-options"
+                color="primary"
+                type="radio"
+                :options="translatedGenderOptions"
+              />
+            </div>
           </div>
 
-          <q-space/>
+          <q-space />
 
-          <div class="q-pa-xl q-mt-md">
+          <div class="q-mt-md q-pa-xl">
             <q-btn
-              unelevated
-              rounded
+              class="full-width q-py-md"
               no-caps
               padding="8px 16px"
-              class="full-width q-py-md"
-              :label="$t('profile.update')"
+              rounded
+              unelevated
               :color="hasUpdatedFields ? 'primary' : 'grey-4'"
               :disable="!hasUpdatedFields"
+              :label="$t('profile.update')"
               :loading="submitting"
               @click="submitUpdate"
             />
@@ -83,79 +157,104 @@
 </template>
 
 <script setup>
-import { ref, onMounted } from 'vue';
-import { useDialogPluginComponent } from 'quasar';
-import { updateUser } from 'src/api/user';
-import { useFormUpdateTracker } from 'src/composables/useFormUpdateTracker';
-
-const avatarStyle = {
-  background: 'linear-gradient(135deg, #ffd7e8 0%, #ff9acc 100%)',
-  color: '#7a154f',
-};
-
-const profilePhoto = ref(null);
+import { computed, onMounted, ref } from "vue";
+import { genderOptions } from "src/helpers/genderOptions";
+import { updateUser } from "src/api/user";
+import { useDialogPluginComponent } from "quasar";
+import { useFormUpdateTracker } from "src/composables/useFormUpdateTracker";
+import { useI18n } from "vue-i18n";
 
 const props = defineProps({
   userData: {
+    default: null,
     type: Object,
-    default: null
-  }
+  },
 });
 
 defineEmits([...useDialogPluginComponent.emits]);
 
-const { dialogRef, onDialogOK, onDialogCancel } = useDialogPluginComponent();
-const loading = ref(false);
-const submitting = ref(false);
-const userId = ref(null);
+const { dialogRef, onDialogCancel, onDialogOK } = useDialogPluginComponent();
+const { t } = useI18n();
 
-const { form, hasUpdatedFields, setUpdateFormAsOriginal } = useFormUpdateTracker({
-  name: '',
-  email: '',
-  phone: ''
-});
+const { form, hasUpdatedFields, setUpdateFormAsOriginal } =
+  useFormUpdateTracker({
+    email: "",
+    gender: "",
+    name: "",
+    phone: "",
+  });
+
+const loading      = ref(false);
+const profilePhoto = ref(null);
+const submitting   = ref(false);
+const userId       = ref(null);
+
+const translatedGenderOptions = computed(() =>
+  genderOptions.map((option) => ({
+    ...option,
+    label: t(option.label),
+  })),
+);
+
+const avatarStyle = {
+  background: "linear-gradient(135deg, #ffd7e8 0%, #ff9acc 100%)", color: "#7a154f",
+};
 
 const submitUpdate = async () => {
   if (!hasUpdatedFields.value) return;
 
   submitting.value = true;
+
   try {
-    const data = await updateUser({
-      name: form.name,
-      email: form.email,
-      phone: form.phone
-    }, userId.value);
-    console.log(data)
-    setUpdateFormAsOriginal(data);
+    const data = await updateUser(
+      {
+        email: form.email,
+        gender: form.gender,
+        name: form.name,
+        phone: form.phone,
+      },
+      userId.value,
+    );
+
+    setUpdateFormAsOriginal();
     onDialogOK(data);
   } catch (error) {
-    console.error('Erro ao atualizar perfil:', error);
+    console.error("Erro ao atualizar perfil:", error);
   } finally {
     submitting.value = false;
   }
 };
 
-onMounted(async () => {
-  if (props.userData) {
-    const data = props.userData;
-    userId.value = data.id;
-    form.name = data.name || '';
-    form.email = data.email || '';
-    form.phone = data.phone || '';
-    profilePhoto.value = data.profile_photo || null;
-    setUpdateFormAsOriginal(data);
+onMounted(() => {
+  if (!props.userData) {
+    loading.value = true;
+
     return;
   }
 
-  loading.value = true;
-});
-</script>
+  const data = props.userData;
 
-<style scoped lang="scss">
+  form.email = data.email || "";
+  form.gender = data.gender || "";
+  form.name = data.name || "";
+  form.phone = data.phone || "";
+  profilePhoto.value = data.profile_photo || null;
+  userId.value = data.id;
 
+  setUpdateFormAsOriginal();
+});
+</script>
 
+<style lang="scss" scoped>
 :deep(.q-field--outlined .q-field__control) {
   border-radius: 8px;
-  &::before { border: 1px solid #e0e0e0; }
+
+  &::before {
+    border: 1px solid #e0e0e0;
+  }
+}
+
+.gender-options :deep(.q-radio__inner--falsy) {
+  color: rgba(0, 0, 0, 0.62);
 }
 </style>